From b46efc9b55c6aaf4dc5f46934ecf2e6d15998e92 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 13 Jan 2016 18:34:38 -0800 Subject: [PATCH] Allow modifies on type members + introduce "readonly" modifier --- src/compiler/checker.ts | 41 ++++++++-- src/compiler/diagnosticMessages.json | 12 ++- src/compiler/parser.ts | 111 +++++++++++---------------- src/compiler/program.ts | 1 + src/compiler/scanner.ts | 1 + src/compiler/types.ts | 33 ++++---- src/compiler/utilities.ts | 2 + 7 files changed, 107 insertions(+), 94 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e73bbea8fae..c73b6309f16 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15675,9 +15675,17 @@ namespace ts { return; } - let lastStatic: Node, lastPrivate: Node, lastProtected: Node, lastDeclare: Node, lastAsync: Node; + let lastStatic: Node, lastPrivate: Node, lastProtected: Node, lastDeclare: Node, lastAsync: Node, lastReadonly: Node; let flags = 0; for (const modifier of node.modifiers) { + if (modifier.kind !== SyntaxKind.ReadonlyKeyword) { + if (node.kind === SyntaxKind.PropertySignature || node.kind === SyntaxKind.MethodSignature) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_type_member, tokenToString(modifier.kind)); + } + if (node.kind === SyntaxKind.IndexSignature) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_an_index_signature, tokenToString(modifier.kind)); + } + } switch (modifier.kind) { case SyntaxKind.ConstKeyword: if (node.kind !== SyntaxKind.EnumDeclaration && node.parent.kind === SyntaxKind.ClassDeclaration) { @@ -15706,6 +15714,9 @@ namespace ts { else if (flags & NodeFlags.Static) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "static"); } + else if (flags & NodeFlags.Readonly) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "readonly"); + } else if (flags & NodeFlags.Async) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "async"); } @@ -15727,6 +15738,9 @@ namespace ts { if (flags & NodeFlags.Static) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "static"); } + else if (flags & NodeFlags.Readonly) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "static", "readonly"); + } else if (flags & NodeFlags.Async) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "static", "async"); } @@ -15743,6 +15757,20 @@ namespace ts { lastStatic = modifier; break; + case SyntaxKind.ReadonlyKeyword: + if (flags & NodeFlags.Readonly) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "readonly"); + } + else if (flags & NodeFlags.Async) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "readonly", "async"); + } + else if (node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.SourceFile) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_module_element, "readonly"); + } + flags |= NodeFlags.Readonly; + lastReadonly = modifier; + break; + case SyntaxKind.ExportKeyword: if (flags & NodeFlags.Export) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "export"); @@ -15839,6 +15867,9 @@ namespace ts { else if (flags & NodeFlags.Async) { return grammarErrorOnNode(lastAsync, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "async"); } + else if (flags & NodeFlags.Readonly) { + return grammarErrorOnNode(lastReadonly, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "readonly"); + } return; } else if ((node.kind === SyntaxKind.ImportDeclaration || node.kind === SyntaxKind.ImportEqualsDeclaration) && flags & NodeFlags.Ambient) { @@ -15984,15 +16015,9 @@ namespace ts { } } - function checkGrammarForIndexSignatureModifier(node: SignatureDeclaration): void { - if (node.flags & NodeFlags.Modifier) { - grammarErrorOnFirstToken(node, Diagnostics.Modifiers_not_permitted_on_index_signature_members); - } - } - function checkGrammarIndexSignature(node: SignatureDeclaration) { // Prevent cascading error by short-circuit - return checkGrammarDecorators(node) || checkGrammarModifiers(node) || checkGrammarIndexSignatureParameters(node) || checkGrammarForIndexSignatureModifier(node); + return checkGrammarDecorators(node) || checkGrammarModifiers(node) || checkGrammarIndexSignatureParameters(node); } function checkGrammarForAtLeastOneTypeArgument(node: Node, typeArguments: NodeArray): boolean { diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 760da6058c1..a10787764d9 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -203,6 +203,14 @@ "category": "Error", "code": 1068 }, + "'{0}' modifier cannot appear on a type member.": { + "category": "Error", + "code": 1070 + }, + "'{0}' modifier cannot appear on an index signature.": { + "category": "Error", + "code": 1071 + }, "A '{0}' modifier cannot be used with an import declaration.": { "category": "Error", "code": 1079 @@ -423,10 +431,6 @@ "category": "Error", "code": 1144 }, - "Modifiers not permitted on index signature members.": { - "category": "Error", - "code": 1145 - }, "Declaration expected.": { "category": "Error", "code": 1146 diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 420d201adc9..6738b9c2d95 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -1192,7 +1192,7 @@ namespace ts { case ParsingContext.SwitchClauses: return token === SyntaxKind.CaseKeyword || token === SyntaxKind.DefaultKeyword; case ParsingContext.TypeMembers: - return isStartOfTypeMember(); + return lookAhead(isTypeMemberStart); case ParsingContext.ClassMembers: // We allow semicolons as class elements (as specified by ES6) as long as we're // not in error recovery. If we're in error recovery, we don't want an errant @@ -2233,13 +2233,13 @@ namespace ts { return finishNode(node); } - function parsePropertyOrMethodSignature(): PropertySignature | MethodSignature { - const fullStart = scanner.getStartPos(); + function parsePropertyOrMethodSignature(fullStart: number, modifiers: ModifiersArray): PropertySignature | MethodSignature { const name = parsePropertyName(); const questionToken = parseOptionalToken(SyntaxKind.QuestionToken); if (token === SyntaxKind.OpenParenToken || token === SyntaxKind.LessThanToken) { const method = createNode(SyntaxKind.MethodSignature, fullStart); + setModifiers(method, modifiers); method.name = name; method.questionToken = questionToken; @@ -2251,6 +2251,7 @@ namespace ts { } else { const property = createNode(SyntaxKind.PropertySignature, fullStart); + setModifiers(property, modifiers); property.name = name; property.questionToken = questionToken; property.type = parseTypeAnnotation(); @@ -2267,77 +2268,51 @@ namespace ts { } } - function isStartOfTypeMember(): boolean { - switch (token) { - case SyntaxKind.OpenParenToken: - case SyntaxKind.LessThanToken: - case SyntaxKind.OpenBracketToken: // Both for indexers and computed properties - return true; - default: - if (isModifierKind(token)) { - const result = lookAhead(isStartOfIndexSignatureDeclaration); - if (result) { - return result; - } - } - - return isLiteralPropertyName() && lookAhead(isTypeMemberWithLiteralPropertyName); + function isTypeMemberStart(): boolean { + let idToken: SyntaxKind; + // Return true if we have the start of a signature member + if (token === SyntaxKind.OpenParenToken || token === SyntaxKind.LessThanToken) { + return true; } - } - - function isStartOfIndexSignatureDeclaration() { + // Eat up all modifiers, but hold on to the last one in case it is actually an identifier while (isModifierKind(token)) { + idToken = token; nextToken(); } - - return isIndexSignature(); - } - - function isTypeMemberWithLiteralPropertyName() { - nextToken(); - return token === SyntaxKind.OpenParenToken || - token === SyntaxKind.LessThanToken || - token === SyntaxKind.QuestionToken || - token === SyntaxKind.ColonToken || - canParseSemicolon(); + // Index signatures and computed property names are type members + if (token === SyntaxKind.OpenBracketToken) { + return true; + } + // Try to get the first property-like token following all modifiers + if (isLiteralPropertyName()) { + idToken = token; + nextToken(); + } + // If we were able to get any potential identifier, check that it is + // the start of a member declaration + if (idToken) { + return token === SyntaxKind.OpenParenToken || + token === SyntaxKind.LessThanToken || + token === SyntaxKind.QuestionToken || + token === SyntaxKind.ColonToken || + canParseSemicolon(); + } + return false; } function parseTypeMember(): TypeElement { - switch (token) { - case SyntaxKind.OpenParenToken: - case SyntaxKind.LessThanToken: - return parseSignatureMember(SyntaxKind.CallSignature); - case SyntaxKind.OpenBracketToken: - // Indexer or computed property - return isIndexSignature() - ? parseIndexSignatureDeclaration(scanner.getStartPos(), /*decorators*/ undefined, /*modifiers*/ undefined) - : parsePropertyOrMethodSignature(); - case SyntaxKind.NewKeyword: - if (lookAhead(isStartOfConstructSignature)) { - return parseSignatureMember(SyntaxKind.ConstructSignature); - } - // fall through. - case SyntaxKind.StringLiteral: - case SyntaxKind.NumericLiteral: - return parsePropertyOrMethodSignature(); - default: - // Index declaration as allowed as a type member. But as per the grammar, - // they also allow modifiers. So we have to check for an index declaration - // that might be following modifiers. This ensures that things work properly - // when incrementally parsing as the parser will produce the Index declaration - // if it has the same text regardless of whether it is inside a class or an - // object type. - if (isModifierKind(token)) { - const result = tryParse(parseIndexSignatureWithModifiers); - if (result) { - return result; - } - } - - if (tokenIsIdentifierOrKeyword(token)) { - return parsePropertyOrMethodSignature(); - } + if (token === SyntaxKind.OpenParenToken || token === SyntaxKind.LessThanToken) { + return parseSignatureMember(SyntaxKind.CallSignature); } + if (token === SyntaxKind.NewKeyword && lookAhead(isStartOfConstructSignature)) { + return parseSignatureMember(SyntaxKind.ConstructSignature); + } + const fullStart = getNodePos(); + const modifiers = parseModifiers(); + if (isIndexSignature()) { + return parseIndexSignatureDeclaration(fullStart, /*decorators*/ undefined, modifiers); + } + return parsePropertyOrMethodSignature(fullStart, modifiers); } function parseIndexSignatureWithModifiers() { @@ -4423,6 +4398,7 @@ namespace ts { case SyntaxKind.PrivateKeyword: case SyntaxKind.ProtectedKeyword: case SyntaxKind.PublicKeyword: + case SyntaxKind.ReadonlyKeyword: nextToken(); // ASI takes effect for this modifier. if (scanner.hasPrecedingLineBreak()) { @@ -4501,6 +4477,7 @@ namespace ts { case SyntaxKind.PrivateKeyword: case SyntaxKind.ProtectedKeyword: case SyntaxKind.StaticKeyword: + case SyntaxKind.ReadonlyKeyword: // When these don't start a declaration, they may be the start of a class member if an identifier // immediately follows. Otherwise they're an identifier in an expression statement. return isStartOfDeclaration() || !lookAhead(nextTokenIsIdentifierOrKeywordOnSameLine); @@ -4582,6 +4559,7 @@ namespace ts { case SyntaxKind.PublicKeyword: case SyntaxKind.AbstractKeyword: case SyntaxKind.StaticKeyword: + case SyntaxKind.ReadonlyKeyword: if (isStartOfDeclaration()) { return parseDeclaration(); } @@ -4868,6 +4846,7 @@ namespace ts { case SyntaxKind.PrivateKeyword: case SyntaxKind.ProtectedKeyword: case SyntaxKind.StaticKeyword: + case SyntaxKind.ReadonlyKeyword: return true; default: return false; diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 6905e01b1e6..849e97ec537 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -836,6 +836,7 @@ namespace ts { case SyntaxKind.PublicKeyword: case SyntaxKind.PrivateKeyword: case SyntaxKind.ProtectedKeyword: + case SyntaxKind.ReadonlyKeyword: case SyntaxKind.DeclareKeyword: diagnostics.push(createDiagnosticForNode(modifier, Diagnostics._0_can_only_be_used_in_a_ts_file, tokenToString(modifier.kind))); return true; diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index 8161ea998c4..b05fbb2e0f1 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -93,6 +93,7 @@ namespace ts { "private": SyntaxKind.PrivateKeyword, "protected": SyntaxKind.ProtectedKeyword, "public": SyntaxKind.PublicKeyword, + "readonly": SyntaxKind.ReadonlyKeyword, "require": SyntaxKind.RequireKeyword, "return": SyntaxKind.ReturnKeyword, "set": SyntaxKind.SetKeyword, diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 9c5690901b3..f6216664d2e 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -163,6 +163,7 @@ namespace ts { IsKeyword, ModuleKeyword, NamespaceKeyword, + ReadonlyKeyword, RequireKeyword, NumberKeyword, SetKeyword, @@ -375,24 +376,24 @@ namespace ts { Private = 1 << 4, // Property/Method Protected = 1 << 5, // Property/Method Static = 1 << 6, // Property/Method - Abstract = 1 << 7, // Class/Method/ConstructSignature - Async = 1 << 8, // Property/Method/Function - Default = 1 << 9, // Function/Class (export default declaration) - MultiLine = 1 << 10, // Multi-line array or object literal - Synthetic = 1 << 11, // Synthetic node (for full fidelity) - DeclarationFile = 1 << 12, // Node is a .d.ts file - Let = 1 << 13, // Variable declaration - Const = 1 << 14, // Variable declaration - OctalLiteral = 1 << 15, // Octal numeric literal - Namespace = 1 << 16, // Namespace declaration - ExportContext = 1 << 17, // Export context (initialized by binding) - ContainsThis = 1 << 18, // Interface contains references to "this" - HasImplicitReturn = 1 << 19, // If function implicitly returns on one of codepaths (initialized by binding) - HasExplicitReturn = 1 << 20, // If function has explicit reachable return on one of codepaths (initialized by binding) - Modifier = Export | Ambient | Public | Private | Protected | Static | Abstract | Default | Async, + Readonly = 1 << 7, // Property/Method + Abstract = 1 << 8, // Class/Method/ConstructSignature + Async = 1 << 9, // Property/Method/Function + Default = 1 << 10, // Function/Class (export default declaration) + MultiLine = 1 << 11, // Multi-line array or object literal + Synthetic = 1 << 12, // Synthetic node (for full fidelity) + DeclarationFile = 1 << 13, // Node is a .d.ts file + Let = 1 << 14, // Variable declaration + Const = 1 << 15, // Variable declaration + OctalLiteral = 1 << 16, // Octal numeric literal + Namespace = 1 << 17, // Namespace declaration + ExportContext = 1 << 18, // Export context (initialized by binding) + ContainsThis = 1 << 19, // Interface contains references to "this" + HasImplicitReturn = 1 << 20, // If function implicitly returns on one of codepaths (initialized by binding) + HasExplicitReturn = 1 << 21, // If function has explicit reachable return on one of codepaths (initialized by binding) + Modifier = Export | Ambient | Public | Private | Protected | Static | Readonly | Abstract | Default | Async, AccessibilityModifier = Public | Private | Protected, BlockScoped = Let | Const, - ReachabilityCheckFlags = HasImplicitReturn | HasExplicitReturn } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 7bc708c7178..bbf73737f22 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1548,6 +1548,7 @@ namespace ts { case SyntaxKind.PublicKeyword: case SyntaxKind.PrivateKeyword: case SyntaxKind.ProtectedKeyword: + case SyntaxKind.ReadonlyKeyword: case SyntaxKind.StaticKeyword: return true; } @@ -2279,6 +2280,7 @@ namespace ts { case SyntaxKind.ConstKeyword: return NodeFlags.Const; case SyntaxKind.DefaultKeyword: return NodeFlags.Default; case SyntaxKind.AsyncKeyword: return NodeFlags.Async; + case SyntaxKind.ReadonlyKeyword: return NodeFlags.Readonly; } return 0; }