From b8cd0e5964f4b999222cbbe6ef1e3da78885d65c Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 18 Jan 2016 08:58:00 -0800 Subject: [PATCH] Support readonly indexers + include readonly modifier in typeToString --- src/compiler/checker.ts | 167 ++++++++++++++------------- src/compiler/diagnosticMessages.json | 4 + src/compiler/types.ts | 2 + 3 files changed, 94 insertions(+), 79 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1f815a6fd3e..75751b45458 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -133,7 +133,7 @@ namespace ts { const anySignature = createSignature(undefined, undefined, emptyArray, anyType, 0, /*hasRestParameter*/ false, /*hasStringLiterals*/ false); const unknownSignature = createSignature(undefined, undefined, emptyArray, unknownType, 0, /*hasRestParameter*/ false, /*hasStringLiterals*/ false); - const enumNumberIndexInfo = createIndexInfo(stringType, /*isReadonly*/ true); + const enumNumberIndexInfo = createIndexInfo(stringType, /*isReadonly*/ false); const globals: SymbolTable = {}; @@ -1956,15 +1956,35 @@ namespace ts { buildSymbolDisplay(type.symbol, writer, enclosingDeclaration, SymbolFlags.Value, SymbolFormatFlags.None, typeFormatFlags); } - function getIndexerParameterName(type: ObjectType, indexKind: IndexKind, fallbackName: string): string { - const declaration = getIndexDeclarationOfSymbol(type.symbol, indexKind); - if (!declaration) { - // declaration might not be found if indexer was added from the contextual type. - // in this case use fallback name - return fallbackName; + function writeIndexSignature(info: IndexInfo, keyword: SyntaxKind) { + if (info) { + if (info.isReadonly) { + writeKeyword(writer, SyntaxKind.ReadonlyKeyword); + writeSpace(writer); + } + writePunctuation(writer, SyntaxKind.OpenBracketToken); + writer.writeParameter(info.declaration ? declarationNameToString(info.declaration.parameters[0].name) : "x"); + writePunctuation(writer, SyntaxKind.ColonToken); + writeSpace(writer); + writeKeyword(writer, keyword); + writePunctuation(writer, SyntaxKind.CloseBracketToken); + writePunctuation(writer, SyntaxKind.ColonToken); + writeSpace(writer); + writeType(info.type, TypeFormatFlags.None); + writePunctuation(writer, SyntaxKind.SemicolonToken); + writer.writeLine(); + } + } + + function writePropertyWithModifiers(prop: Symbol) { + if (isReadonlySymbol(prop)) { + writeKeyword(writer, SyntaxKind.ReadonlyKeyword); + writeSpace(writer); + } + buildSymbolDisplay(prop, writer); + if (prop.flags & SymbolFlags.Optional) { + writePunctuation(writer, SyntaxKind.QuestionToken); } - Debug.assert(declaration.parameters.length !== 0); - return declarationNameToString(declaration.parameters[0].name); } function writeLiteralType(type: ObjectType, flags: TypeFormatFlags) { @@ -2015,53 +2035,21 @@ namespace ts { writePunctuation(writer, SyntaxKind.SemicolonToken); writer.writeLine(); } - if (resolved.stringIndexInfo) { - // [x: string]: - writePunctuation(writer, SyntaxKind.OpenBracketToken); - writer.writeParameter(getIndexerParameterName(resolved, IndexKind.String, /*fallbackName*/"x")); - writePunctuation(writer, SyntaxKind.ColonToken); - writeSpace(writer); - writeKeyword(writer, SyntaxKind.StringKeyword); - writePunctuation(writer, SyntaxKind.CloseBracketToken); - writePunctuation(writer, SyntaxKind.ColonToken); - writeSpace(writer); - writeType(resolved.stringIndexInfo.type, TypeFormatFlags.None); - writePunctuation(writer, SyntaxKind.SemicolonToken); - writer.writeLine(); - } - if (resolved.numberIndexInfo) { - // [x: number]: - writePunctuation(writer, SyntaxKind.OpenBracketToken); - writer.writeParameter(getIndexerParameterName(resolved, IndexKind.Number, /*fallbackName*/"x")); - writePunctuation(writer, SyntaxKind.ColonToken); - writeSpace(writer); - writeKeyword(writer, SyntaxKind.NumberKeyword); - writePunctuation(writer, SyntaxKind.CloseBracketToken); - writePunctuation(writer, SyntaxKind.ColonToken); - writeSpace(writer); - writeType(resolved.numberIndexInfo.type, TypeFormatFlags.None); - writePunctuation(writer, SyntaxKind.SemicolonToken); - writer.writeLine(); - } + writeIndexSignature(resolved.stringIndexInfo, SyntaxKind.StringKeyword); + writeIndexSignature(resolved.numberIndexInfo, SyntaxKind.NumberKeyword); for (const p of resolved.properties) { const t = getTypeOfSymbol(p); if (p.flags & (SymbolFlags.Function | SymbolFlags.Method) && !getPropertiesOfObjectType(t).length) { const signatures = getSignaturesOfType(t, SignatureKind.Call); for (const signature of signatures) { - buildSymbolDisplay(p, writer); - if (p.flags & SymbolFlags.Optional) { - writePunctuation(writer, SyntaxKind.QuestionToken); - } + writePropertyWithModifiers(p); buildSignatureDisplay(signature, writer, enclosingDeclaration, globalFlagsToPass, /*kind*/ undefined, symbolStack); writePunctuation(writer, SyntaxKind.SemicolonToken); writer.writeLine(); } } else { - buildSymbolDisplay(p, writer); - if (p.flags & SymbolFlags.Optional) { - writePunctuation(writer, SyntaxKind.QuestionToken); - } + writePropertyWithModifiers(p); writePunctuation(writer, SyntaxKind.ColonToken); writeSpace(writer); writeType(t, TypeFormatFlags.None); @@ -4125,15 +4113,15 @@ namespace ts { return undefined; } - function createIndexInfo(type: Type, isReadonly: boolean): IndexInfo { - return { type, isReadonly }; + function createIndexInfo(type: Type, isReadonly: boolean, declaration?: SignatureDeclaration): IndexInfo { + return { type, isReadonly, declaration }; } function getIndexInfoOfSymbol(symbol: Symbol, kind: IndexKind): IndexInfo { const declaration = getIndexDeclarationOfSymbol(symbol, kind); if (declaration) { return createIndexInfo(declaration.type ? getTypeFromTypeNode(declaration.type) : anyType, - (declaration.flags & NodeFlags.Readonly) !== 0); + (declaration.flags & NodeFlags.Readonly) !== 0, declaration); } return undefined; } @@ -4873,7 +4861,7 @@ namespace ts { } function instantiateIndexInfo(info: IndexInfo, mapper: TypeMapper): IndexInfo { - return info && createIndexInfo(instantiateType(info.type, mapper), info.isReadonly); + return info && createIndexInfo(instantiateType(info.type, mapper), info.isReadonly, info.declaration); } // Returns true if the given expression contains (at any level of nesting) a function or arrow expression @@ -5739,27 +5727,34 @@ namespace ts { if (relation === identityRelation) { return indexTypesIdenticalTo(IndexKind.String, source, target); } - const targetType = getIndexTypeOfType(target, IndexKind.String); - if (targetType) { - if ((targetType.flags & TypeFlags.Any) && !(originalSource.flags & TypeFlags.Primitive)) { + const targetInfo = getIndexInfoOfType(target, IndexKind.String); + if (targetInfo) { + if ((targetInfo.type.flags & TypeFlags.Any) && !(originalSource.flags & TypeFlags.Primitive)) { // non-primitive assignment to any is always allowed, eg // `var x: { [index: string]: any } = { property: 12 };` return Ternary.True; } - const sourceType = getIndexTypeOfType(source, IndexKind.String); - if (!sourceType) { + const sourceInfo = getIndexInfoOfType(source, IndexKind.String); + if (!sourceInfo) { if (reportErrors) { reportError(Diagnostics.Index_signature_is_missing_in_type_0, typeToString(source)); } return Ternary.False; } - const related = isRelatedTo(sourceType, targetType, reportErrors); + const related = isRelatedTo(sourceInfo.type, targetInfo.type, reportErrors); if (!related) { if (reportErrors) { reportError(Diagnostics.Index_signatures_are_incompatible); } return Ternary.False; } + if (sourceInfo.isReadonly && !targetInfo.isReadonly) { + if (reportErrors) { + reportError(Diagnostics.Index_signature_is_read_only_in_type_0_but_read_write_in_type_1, + typeToString(source), typeToString(target)); + } + return Ternary.False; + } return related; } return Ternary.True; @@ -5769,28 +5764,29 @@ namespace ts { if (relation === identityRelation) { return indexTypesIdenticalTo(IndexKind.Number, source, target); } - const targetType = getIndexTypeOfType(target, IndexKind.Number); - if (targetType) { - if ((targetType.flags & TypeFlags.Any) && !(originalSource.flags & TypeFlags.Primitive)) { + const targetInfo = getIndexInfoOfType(target, IndexKind.Number); + if (targetInfo) { + if ((targetInfo.type.flags & TypeFlags.Any) && !(originalSource.flags & TypeFlags.Primitive)) { // non-primitive assignment to any is always allowed, eg // `var x: { [index: number]: any } = { property: 12 };` return Ternary.True; } - const sourceStringType = getIndexTypeOfType(source, IndexKind.String); - const sourceNumberType = getIndexTypeOfType(source, IndexKind.Number); - if (!(sourceStringType || sourceNumberType)) { + const sourceStringInfo = getIndexInfoOfType(source, IndexKind.String); + const sourceNumberInfo = getIndexInfoOfType(source, IndexKind.Number); + if (!(sourceStringInfo || sourceNumberInfo)) { if (reportErrors) { reportError(Diagnostics.Index_signature_is_missing_in_type_0, typeToString(source)); } return Ternary.False; } let related: Ternary; - if (sourceStringType && sourceNumberType) { + if (sourceStringInfo && sourceNumberInfo) { // If we know for sure we're testing both string and numeric index types then only report errors from the second one - related = isRelatedTo(sourceStringType, targetType, /*reportErrors*/ false) || isRelatedTo(sourceNumberType, targetType, reportErrors); + related = isRelatedTo(sourceStringInfo.type, targetInfo.type, /*reportErrors*/ false) || + isRelatedTo(sourceNumberInfo.type, targetInfo.type, reportErrors); } else { - related = isRelatedTo(sourceStringType || sourceNumberType, targetType, reportErrors); + related = isRelatedTo((sourceStringInfo || sourceNumberInfo).type, targetInfo.type, reportErrors); } if (!related) { if (reportErrors) { @@ -5798,19 +5794,26 @@ namespace ts { } return Ternary.False; } + if ((sourceStringInfo && sourceStringInfo.isReadonly || sourceNumberInfo && sourceNumberInfo.isReadonly) && !targetInfo.isReadonly) { + if (reportErrors) { + reportError(Diagnostics.Index_signature_is_read_only_in_type_0_but_read_write_in_type_1, + typeToString(source), typeToString(target)); + } + return Ternary.False; + } return related; } return Ternary.True; } function indexTypesIdenticalTo(indexKind: IndexKind, source: Type, target: Type): Ternary { - const targetType = getIndexTypeOfType(target, indexKind); - const sourceType = getIndexTypeOfType(source, indexKind); - if (!sourceType && !targetType) { + const targetInfo = getIndexInfoOfType(target, indexKind); + const sourceInfo = getIndexInfoOfType(source, indexKind); + if (!sourceInfo && !targetInfo) { return Ternary.True; } - if (sourceType && targetType) { - return isRelatedTo(sourceType, targetType); + if (sourceInfo && targetInfo && sourceInfo.isReadonly === targetInfo.isReadonly) { + return isRelatedTo(sourceInfo.type, targetInfo.type); } return Ternary.False; } @@ -8719,16 +8722,18 @@ namespace ts { // Try to use a number indexer. if (isTypeAnyOrAllConstituentTypesHaveKind(indexType, TypeFlags.NumberLike) || isForInVariableForNumericPropertyNames(node.argumentExpression)) { - const numberIndexType = getIndexTypeOfType(objectType, IndexKind.Number); - if (numberIndexType) { - return numberIndexType; + const numberIndexInfo = getIndexInfoOfType(objectType, IndexKind.Number); + if (numberIndexInfo) { + getNodeLinks(node).resolvedIndexInfo = numberIndexInfo; + return numberIndexInfo.type; } } // Try to use string indexing. - const stringIndexType = getIndexTypeOfType(objectType, IndexKind.String); - if (stringIndexType) { - return stringIndexType; + const stringIndexInfo = getIndexInfoOfType(objectType, IndexKind.String); + if (stringIndexInfo) { + getNodeLinks(node).resolvedIndexInfo = stringIndexInfo; + return stringIndexInfo.type; } // Fall back to any. @@ -10384,10 +10389,11 @@ namespace ts { error(expr, invalidReferenceMessage); return false; } - // Because we got the symbol from the resolvedSymbol property, it might be of kind + // Because we get 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); + const links = getNodeLinks(node); + const symbol = getExportSymbolOfValueSymbolIfExported(links.resolvedSymbol); if (symbol) { if (symbol !== unknownSymbol && symbol !== argumentsSymbol) { if (symbol === undefinedSymbol || !(symbol.flags & (SymbolFlags.Variable | SymbolFlags.Property | SymbolFlags.Method | SymbolFlags.Accessor))) { @@ -10400,8 +10406,11 @@ namespace ts { } } } - else { - // Check indexers + else if (node.kind === SyntaxKind.ElementAccessExpression) { + if (links.resolvedIndexInfo && links.resolvedIndexInfo.isReadonly) { + error(expr, constantVariableMessage); + return false; + } } return true; } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 98a05f5a302..6599a35f494 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1083,6 +1083,10 @@ "category": "Error", "code": 2365 }, + "Index signature is read-only in type '{0}' but read-write in type '{1}'.": { + "category": "Error", + "code": 2366 + }, "Type parameter name cannot be '{0}'": { "category": "Error", "code": 2368 diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 1ccb9e4067c..0a8cbb3abd3 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2068,6 +2068,7 @@ namespace ts { resolvedAwaitedType?: Type; // Cached awaited type of type node resolvedSignature?: Signature; // Cached signature of signature node or call expression resolvedSymbol?: Symbol; // Cached name resolution result + resolvedIndexInfo?: IndexInfo; // Cached indexing info resolution result flags?: NodeCheckFlags; // Set of flags specific to Node enumMemberValue?: number; // Constant value of enum member isVisible?: boolean; // Is this node visible @@ -2295,6 +2296,7 @@ namespace ts { export interface IndexInfo { type: Type; isReadonly: boolean; + declaration?: SignatureDeclaration; } /* @internal */