diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 657683323604f..ba84a5d4cce8e 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -242,6 +242,22 @@ namespace ts { addDeclarationToSymbol(symbol, node, includes); symbol.parent = parent; + if (node.flags & NodeFlags.Const || + (node.kind === SyntaxKind.VariableDeclaration && node.parent.flags & NodeFlags.Const)) { + if (symbol.flags & SymbolFlags.ConstEnum) { + symbol.constraints |= SymbolConstraints.Immutable; + } + else { + symbol.constraints |= SymbolConstraints.notWritable; + } + } + + // Only true for 'const enum' which its members are implicitly not writable + // Note: Immutable class/container could exist iff all its members are notWritable/const + if(symbol.parent && symbol.parent.constraints & SymbolConstraints.Immutable) { + symbol.constraints |= SymbolConstraints.notWritable; + } + return symbol; } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 551f428ca9ac2..20f51d6e77c65 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -9744,7 +9744,7 @@ namespace ts { return !symbol || symbol === unknownSymbol || (symbol.flags & ~SymbolFlags.EnumMember) !== 0; } case SyntaxKind.ElementAccessExpression: - // old compiler doesn't check indexed assess + // old compiler doesn't check indexed access return true; case SyntaxKind.ParenthesizedExpression: return isReferenceOrErrorExpression((n).expression); @@ -9753,12 +9753,12 @@ namespace ts { } } - function isConstVariableReference(n: Node): boolean { + function isNotWritableVariableReference(n: Node): boolean { switch (n.kind) { case SyntaxKind.Identifier: case SyntaxKind.PropertyAccessExpression: { let symbol = findSymbol(n); - return symbol && (symbol.flags & SymbolFlags.Variable) !== 0 && (getDeclarationFlagsFromSymbol(symbol) & NodeFlags.Const) !== 0; + return symbol && (symbol.constraints & SymbolConstraints.notWritable) !== 0; } case SyntaxKind.ElementAccessExpression: { let index = (n).argumentExpression; @@ -9766,12 +9766,12 @@ namespace ts { if (symbol && index && index.kind === SyntaxKind.StringLiteral) { let name = (index).text; let prop = getPropertyOfType(getTypeOfSymbol(symbol), name); - return prop && (prop.flags & SymbolFlags.Variable) !== 0 && (getDeclarationFlagsFromSymbol(prop) & NodeFlags.Const) !== 0; + return prop && (prop.constraints & SymbolConstraints.notWritable) !== 0; } return false; } case SyntaxKind.ParenthesizedExpression: - return isConstVariableReference((n).expression); + return isNotWritableVariableReference((n).expression); default: return false; } @@ -9782,7 +9782,7 @@ namespace ts { return false; } - if (isConstVariableReference(n)) { + if (isNotWritableVariableReference(n)) { error(n, constantVariableMessage); return false; } diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index dcdaec983184b..eb6177f11bc2d 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -955,8 +955,11 @@ namespace ts { createMissingNode(t, reportAtCurrentPosition, diagnosticMessage, arg0); } - function parseTokenNode(): T { + function parseTokenNode(flags?: NodeFlags): T { let node = createNode(token); + if (flags) { + node.flags = flags; + } nextToken(); return finishNode(node); } @@ -1849,7 +1852,7 @@ namespace ts { function parseTemplateExpression(): TemplateExpression { let template = createNode(SyntaxKind.TemplateExpression); - template.head = parseLiteralNode(); + template.head = parseLiteralNode(/*internName*/ false, NodeFlags.ConstValue); Debug.assert(template.head.kind === SyntaxKind.TemplateHead, "Template head has wrong token kind"); let templateSpans = >[]; @@ -1884,11 +1887,13 @@ namespace ts { return finishNode(span); } - function parseLiteralNode(internName?: boolean): LiteralExpression { + function parseLiteralNode(internName?: boolean, flags?: NodeFlags): LiteralExpression { let node = createNode(token); let text = scanner.getTokenValue(); node.text = internName ? internIdentifier(text) : text; - + if (flags) { + node.flags = flags; + } if (scanner.hasExtendedUnicodeEscape()) { node.hasExtendedUnicodeEscape = true; } @@ -3651,13 +3656,14 @@ namespace ts { case SyntaxKind.NumericLiteral: case SyntaxKind.StringLiteral: case SyntaxKind.NoSubstitutionTemplateLiteral: - return parseLiteralNode(); + return parseLiteralNode(/* internName */ false, NodeFlags.ConstValue); case SyntaxKind.ThisKeyword: case SyntaxKind.SuperKeyword: + return parseTokenNode(); case SyntaxKind.NullKeyword: case SyntaxKind.TrueKeyword: case SyntaxKind.FalseKeyword: - return parseTokenNode(); + return parseTokenNode(NodeFlags.ConstValue); case SyntaxKind.OpenParenToken: return parseParenthesizedExpression(); case SyntaxKind.OpenBracketToken: diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 773083d49b64f..811bf2a7f934a 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -377,10 +377,12 @@ namespace ts { Namespace = 0x00020000, // Namespace declaration ExportContext = 0x00040000, // Export context (initialized by binding) ContainsThis = 0x00080000, // Interface contains references to "this" + ConstValue = 0x00100000, // Node that represents a constant value (1, "a", true/false, null) Modifier = Export | Ambient | Public | Private | Protected | Static | Abstract | Default | Async, AccessibilityModifier = Public | Private | Protected, - BlockScoped = Let | Const + BlockScoped = Let | Const, + Constant = Const | ConstValue } /* @internal */ @@ -1693,6 +1695,7 @@ namespace ts { name: string; // Name of symbol declarations?: Declaration[]; // Declarations associated with this symbol valueDeclaration?: Declaration; // First value declaration of the symbol + constraints?: SymbolConstraints; // Symbol constraints members?: SymbolTable; // Class, interface or literal instance members exports?: SymbolTable; // Module exports @@ -1703,6 +1706,12 @@ namespace ts { /* @internal */ constEnumOnlyModule?: boolean; // True if module contains only const enums or other modules with only const enums } + export const enum SymbolConstraints { + notWritable = 1, + notMutable, + Immutable = notWritable | notMutable, + } + /* @internal */ export interface SymbolLinks { target?: Symbol; // Resolved (non-alias) target of an alias