From d50435736bf54562581db4c6eccc97f0afa460e5 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 16 Jan 2016 12:09:18 -0800 Subject: [PATCH] Check readonly in assignments and type relations --- src/compiler/checker.ts | 108 ++++++++++----------------- src/compiler/diagnosticMessages.json | 4 + 2 files changed, 45 insertions(+), 67 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c73b6309f16..7ad6a5d6d7a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5523,6 +5523,13 @@ namespace ts { } return Ternary.False; } + if (isReadonlySymbol(sourceProp) && !isReadonlySymbol(targetProp)) { + if (reportErrors) { + reportError(Diagnostics.Property_0_is_read_only_in_type_1_but_read_write_in_type_2, + symbolToString(targetProp), typeToString(source), typeToString(target)); + } + return Ternary.False; + } } } } @@ -10323,81 +10330,48 @@ namespace ts { return true; } - function checkReferenceExpression(n: Node, invalidReferenceMessage: DiagnosticMessage, constantVariableMessage: DiagnosticMessage): boolean { - function findSymbol(n: Node): Symbol { - const symbol = getNodeLinks(n).resolvedSymbol; - // Because we got the symbol from the resolvedSymbol property, it might be of kind - // SymbolFlags.ExportValue. In this case it is necessary to get the actual export - // symbol, which will have the correct flags set on it. - return symbol && getExportSymbolOfValueSymbolIfExported(symbol); + function isReadonlySymbol(symbol: Symbol): boolean { + if (symbol.flags & (SymbolFlags.Property | SymbolFlags.Method)) { + return symbol === undefinedSymbol || (getDeclarationFlagsFromSymbol(symbol) & NodeFlags.Readonly) !== 0; } - - function isReferenceOrErrorExpression(n: Node): boolean { - // TypeScript 1.0 spec (April 2014): - // Expressions are classified as values or references. - // References are the subset of expressions that are permitted as the target of an assignment. - // Specifically, references are combinations of identifiers(section 4.3), parentheses(section 4.7), - // and property accesses(section 4.10). - // All other expression constructs described in this chapter are classified as values. - switch (n.kind) { - case SyntaxKind.Identifier: { - const symbol = findSymbol(n); - // TypeScript 1.0 spec (April 2014): 4.3 - // An identifier expression that references a variable or parameter is classified as a reference. - // An identifier expression that references any other kind of entity is classified as a value(and therefore cannot be the target of an assignment). - return !symbol || symbol === unknownSymbol || symbol === argumentsSymbol || (symbol.flags & SymbolFlags.Variable) !== 0; - } - case SyntaxKind.PropertyAccessExpression: { - const symbol = findSymbol(n); - // TypeScript 1.0 spec (April 2014): 4.10 - // A property access expression is always classified as a reference. - // NOTE (not in spec): assignment to enum members should not be allowed - return !symbol || symbol === unknownSymbol || (symbol.flags & ~SymbolFlags.EnumMember) !== 0; - } - case SyntaxKind.ElementAccessExpression: - // old compiler doesn't check indexed access - return true; - case SyntaxKind.ParenthesizedExpression: - return isReferenceOrErrorExpression((n).expression); - default: - return false; - } + if (symbol.flags & SymbolFlags.Accessor) { + return !(symbol.flags & SymbolFlags.SetAccessor); } + return false; + } - function isConstVariableReference(n: Node): boolean { - switch (n.kind) { - case SyntaxKind.Identifier: - case SyntaxKind.PropertyAccessExpression: { - const symbol = findSymbol(n); - return symbol && (symbol.flags & SymbolFlags.Variable) !== 0 && (getDeclarationFlagsFromSymbol(symbol) & NodeFlags.Const) !== 0; - } - case SyntaxKind.ElementAccessExpression: { - const index = (n).argumentExpression; - const symbol = findSymbol((n).expression); - if (symbol && index && index.kind === SyntaxKind.StringLiteral) { - const name = (index).text; - const prop = getPropertyOfType(getTypeOfSymbol(symbol), name); - return prop && (prop.flags & SymbolFlags.Variable) !== 0 && (getDeclarationFlagsFromSymbol(prop) & NodeFlags.Const) !== 0; - } - return false; - } - case SyntaxKind.ParenthesizedExpression: - return isConstVariableReference((n).expression); - default: - return false; - } + function isConstantSymbol(symbol: Symbol): boolean { + if (symbol.flags & SymbolFlags.Variable) { + return (getDeclarationFlagsFromSymbol(symbol) & NodeFlags.Const) !== 0; } + } - if (!isReferenceOrErrorExpression(n)) { - error(n, invalidReferenceMessage); + function checkReferenceExpression(expr: Expression, invalidReferenceMessage: DiagnosticMessage, constantVariableMessage: DiagnosticMessage): boolean { + // References are combinations of identifiers, parentheses, and property accesses. + const node = skipParenthesizedNodes(expr); + if (node.kind !== SyntaxKind.Identifier && node.kind !== SyntaxKind.PropertyAccessExpression && node.kind !== SyntaxKind.ElementAccessExpression) { + error(expr, invalidReferenceMessage); return false; } - - if (isConstVariableReference(n)) { - error(n, constantVariableMessage); - return false; + // Because we got the symbol from the resolvedSymbol property, it might be of kind + // SymbolFlags.ExportValue. In this case it is necessary to get the actual export + // symbol, which will have the correct flags set on it. + const symbol = getExportSymbolOfValueSymbolIfExported(getNodeLinks(node).resolvedSymbol); + if (symbol) { + if (symbol !== unknownSymbol && symbol !== argumentsSymbol) { + if (symbol === undefinedSymbol || !(symbol.flags & (SymbolFlags.Variable | SymbolFlags.Property | SymbolFlags.Method | SymbolFlags.Accessor))) { + error(expr, invalidReferenceMessage); + return false; + } + if (isReadonlySymbol(symbol) || isConstantSymbol(symbol)) { + error(expr, constantVariableMessage); + return false; + } + } + } + else { + // Check indexers } - return true; } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index a10787764d9..98a05f5a302 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -991,6 +991,10 @@ "category": "Error", "code": 2342 }, + "Property '{0}' is read-only in type '{1}' but read-write in type '{2}'.": { + "category": "Error", + "code": 2343 + }, "Type '{0}' does not satisfy the constraint '{1}'.": { "category": "Error", "code": 2344