diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7ad6a5d6d7a..1f815a6fd3e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -133,6 +133,8 @@ 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 globals: SymbolTable = {}; let globalESSymbolConstructorSymbol: Symbol; @@ -1319,19 +1321,19 @@ namespace ts { return result || emptyArray; } - function setObjectTypeMembers(type: ObjectType, members: SymbolTable, callSignatures: Signature[], constructSignatures: Signature[], stringIndexType: Type, numberIndexType: Type): ResolvedType { + function setObjectTypeMembers(type: ObjectType, members: SymbolTable, callSignatures: Signature[], constructSignatures: Signature[], stringIndexInfo: IndexInfo, numberIndexInfo: IndexInfo): ResolvedType { (type).members = members; (type).properties = getNamedMembers(members); (type).callSignatures = callSignatures; (type).constructSignatures = constructSignatures; - if (stringIndexType) (type).stringIndexType = stringIndexType; - if (numberIndexType) (type).numberIndexType = numberIndexType; + if (stringIndexInfo) (type).stringIndexInfo = stringIndexInfo; + if (numberIndexInfo) (type).numberIndexInfo = numberIndexInfo; return type; } - function createAnonymousType(symbol: Symbol, members: SymbolTable, callSignatures: Signature[], constructSignatures: Signature[], stringIndexType: Type, numberIndexType: Type): ResolvedType { + function createAnonymousType(symbol: Symbol, members: SymbolTable, callSignatures: Signature[], constructSignatures: Signature[], stringIndexInfo: IndexInfo, numberIndexInfo: IndexInfo): ResolvedType { return setObjectTypeMembers(createObjectType(TypeFlags.Anonymous, symbol), - members, callSignatures, constructSignatures, stringIndexType, numberIndexType); + members, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo); } function forEachSymbolTableInScope(enclosingDeclaration: Node, callback: (symbolTable: SymbolTable) => T): T { @@ -1967,7 +1969,7 @@ namespace ts { function writeLiteralType(type: ObjectType, flags: TypeFormatFlags) { const resolved = resolveStructuredTypeMembers(type); - if (!resolved.properties.length && !resolved.stringIndexType && !resolved.numberIndexType) { + if (!resolved.properties.length && !resolved.stringIndexInfo && !resolved.numberIndexInfo) { if (!resolved.callSignatures.length && !resolved.constructSignatures.length) { writePunctuation(writer, SyntaxKind.OpenBraceToken); writePunctuation(writer, SyntaxKind.CloseBraceToken); @@ -2013,7 +2015,7 @@ namespace ts { writePunctuation(writer, SyntaxKind.SemicolonToken); writer.writeLine(); } - if (resolved.stringIndexType) { + if (resolved.stringIndexInfo) { // [x: string]: writePunctuation(writer, SyntaxKind.OpenBracketToken); writer.writeParameter(getIndexerParameterName(resolved, IndexKind.String, /*fallbackName*/"x")); @@ -2023,11 +2025,11 @@ namespace ts { writePunctuation(writer, SyntaxKind.CloseBracketToken); writePunctuation(writer, SyntaxKind.ColonToken); writeSpace(writer); - writeType(resolved.stringIndexType, TypeFormatFlags.None); + writeType(resolved.stringIndexInfo.type, TypeFormatFlags.None); writePunctuation(writer, SyntaxKind.SemicolonToken); writer.writeLine(); } - if (resolved.numberIndexType) { + if (resolved.numberIndexInfo) { // [x: number]: writePunctuation(writer, SyntaxKind.OpenBracketToken); writer.writeParameter(getIndexerParameterName(resolved, IndexKind.Number, /*fallbackName*/"x")); @@ -2037,7 +2039,7 @@ namespace ts { writePunctuation(writer, SyntaxKind.CloseBracketToken); writePunctuation(writer, SyntaxKind.ColonToken); writeSpace(writer); - writeType(resolved.numberIndexType, TypeFormatFlags.None); + writeType(resolved.numberIndexInfo.type, TypeFormatFlags.None); writePunctuation(writer, SyntaxKind.SemicolonToken); writer.writeLine(); } @@ -3343,8 +3345,8 @@ namespace ts { (type).declaredProperties = getNamedMembers(symbol.members); (type).declaredCallSignatures = getSignaturesOfSymbol(symbol.members["__call"]); (type).declaredConstructSignatures = getSignaturesOfSymbol(symbol.members["__new"]); - (type).declaredStringIndexType = getIndexTypeOfSymbol(symbol, IndexKind.String); - (type).declaredNumberIndexType = getIndexTypeOfSymbol(symbol, IndexKind.Number); + (type).declaredStringIndexInfo = getIndexInfoOfSymbol(symbol, IndexKind.String); + (type).declaredNumberIndexInfo = getIndexInfoOfSymbol(symbol, IndexKind.Number); } return type; } @@ -3362,15 +3364,15 @@ namespace ts { let members = source.symbol.members; let callSignatures = source.declaredCallSignatures; let constructSignatures = source.declaredConstructSignatures; - let stringIndexType = source.declaredStringIndexType; - let numberIndexType = source.declaredNumberIndexType; + let stringIndexInfo = source.declaredStringIndexInfo; + let numberIndexInfo = source.declaredNumberIndexInfo; if (!rangeEquals(typeParameters, typeArguments, 0, typeParameters.length)) { mapper = createTypeMapper(typeParameters, typeArguments); members = createInstantiatedSymbolTable(source.declaredProperties, mapper, /*mappingThisOnly*/ typeParameters.length === 1); callSignatures = instantiateList(source.declaredCallSignatures, mapper, instantiateSignature); constructSignatures = instantiateList(source.declaredConstructSignatures, mapper, instantiateSignature); - stringIndexType = instantiateType(source.declaredStringIndexType, mapper); - numberIndexType = instantiateType(source.declaredNumberIndexType, mapper); + stringIndexInfo = instantiateIndexInfo(source.declaredStringIndexInfo, mapper); + numberIndexInfo = instantiateIndexInfo(source.declaredNumberIndexInfo, mapper); } const baseTypes = getBaseTypes(source); if (baseTypes.length) { @@ -3383,11 +3385,11 @@ namespace ts { addInheritedMembers(members, getPropertiesOfObjectType(instantiatedBaseType)); callSignatures = concatenate(callSignatures, getSignaturesOfType(instantiatedBaseType, SignatureKind.Call)); constructSignatures = concatenate(constructSignatures, getSignaturesOfType(instantiatedBaseType, SignatureKind.Construct)); - stringIndexType = stringIndexType || getIndexTypeOfType(instantiatedBaseType, IndexKind.String); - numberIndexType = numberIndexType || getIndexTypeOfType(instantiatedBaseType, IndexKind.Number); + stringIndexInfo = stringIndexInfo || getIndexInfoOfType(instantiatedBaseType, IndexKind.String); + numberIndexInfo = numberIndexInfo || getIndexInfoOfType(instantiatedBaseType, IndexKind.Number); } } - setObjectTypeMembers(type, members, callSignatures, constructSignatures, stringIndexType, numberIndexType); + setObjectTypeMembers(type, members, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo); } function resolveClassOrInterfaceMembers(type: InterfaceType): void { @@ -3458,7 +3460,7 @@ namespace ts { const arrayType = resolveStructuredTypeMembers(createTypeFromGenericGlobalType(globalArrayType, [arrayElementType, type])); const members = createTupleTypeMemberSymbols(type.elementTypes); addInheritedMembers(members, arrayType.properties); - setObjectTypeMembers(type, members, arrayType.callSignatures, arrayType.constructSignatures, arrayType.stringIndexType, arrayType.numberIndexType); + setObjectTypeMembers(type, members, arrayType.callSignatures, arrayType.constructSignatures, arrayType.stringIndexInfo, arrayType.numberIndexInfo); } function findMatchingSignature(signatureList: Signature[], signature: Signature, partialMatch: boolean, ignoreReturnTypes: boolean): Signature { @@ -3526,16 +3528,18 @@ namespace ts { return result || emptyArray; } - function getUnionIndexType(types: Type[], kind: IndexKind): Type { + function getUnionIndexInfo(types: Type[], kind: IndexKind): IndexInfo { const indexTypes: Type[] = []; + let isAnyReadonly = false; for (const type of types) { - const indexType = getIndexTypeOfType(type, kind); - if (!indexType) { + const indexInfo = getIndexInfoOfType(type, kind); + if (!indexInfo) { return undefined; } - indexTypes.push(indexType); + indexTypes.push(indexInfo.type); + isAnyReadonly = isAnyReadonly || indexInfo.isReadonly; } - return getUnionType(indexTypes); + return createIndexInfo(getUnionType(indexTypes), isAnyReadonly); } function resolveUnionTypeMembers(type: UnionType) { @@ -3543,29 +3547,34 @@ namespace ts { // type use getPropertiesOfType (only the language service uses this). const callSignatures = getUnionSignatures(type.types, SignatureKind.Call); const constructSignatures = getUnionSignatures(type.types, SignatureKind.Construct); - const stringIndexType = getUnionIndexType(type.types, IndexKind.String); - const numberIndexType = getUnionIndexType(type.types, IndexKind.Number); - setObjectTypeMembers(type, emptySymbols, callSignatures, constructSignatures, stringIndexType, numberIndexType); + const stringIndexInfo = getUnionIndexInfo(type.types, IndexKind.String); + const numberIndexInfo = getUnionIndexInfo(type.types, IndexKind.Number); + setObjectTypeMembers(type, emptySymbols, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo); } function intersectTypes(type1: Type, type2: Type): Type { return !type1 ? type2 : !type2 ? type1 : getIntersectionType([type1, type2]); } + function intersectIndexInfos(info1: IndexInfo, info2: IndexInfo): IndexInfo { + return !info1 ? info2 : !info2 ? info1 : createIndexInfo( + getIntersectionType([info1.type, info2.type]), info1.isReadonly && info2.isReadonly); + } + function resolveIntersectionTypeMembers(type: IntersectionType) { // The members and properties collections are empty for intersection types. To get all properties of an // intersection type use getPropertiesOfType (only the language service uses this). let callSignatures: Signature[] = emptyArray; let constructSignatures: Signature[] = emptyArray; - let stringIndexType: Type = undefined; - let numberIndexType: Type = undefined; + let stringIndexInfo: IndexInfo = undefined; + let numberIndexInfo: IndexInfo = undefined; for (const t of type.types) { callSignatures = concatenate(callSignatures, getSignaturesOfType(t, SignatureKind.Call)); constructSignatures = concatenate(constructSignatures, getSignaturesOfType(t, SignatureKind.Construct)); - stringIndexType = intersectTypes(stringIndexType, getIndexTypeOfType(t, IndexKind.String)); - numberIndexType = intersectTypes(numberIndexType, getIndexTypeOfType(t, IndexKind.Number)); + stringIndexInfo = intersectIndexInfos(stringIndexInfo, getIndexInfoOfType(t, IndexKind.String)); + numberIndexInfo = intersectIndexInfos(numberIndexInfo, getIndexInfoOfType(t, IndexKind.Number)); } - setObjectTypeMembers(type, emptySymbols, callSignatures, constructSignatures, stringIndexType, numberIndexType); + setObjectTypeMembers(type, emptySymbols, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo); } function resolveAnonymousTypeMembers(type: AnonymousType) { @@ -3574,17 +3583,17 @@ namespace ts { const members = createInstantiatedSymbolTable(getPropertiesOfObjectType(type.target), type.mapper, /*mappingThisOnly*/ false); const callSignatures = instantiateList(getSignaturesOfType(type.target, SignatureKind.Call), type.mapper, instantiateSignature); const constructSignatures = instantiateList(getSignaturesOfType(type.target, SignatureKind.Construct), type.mapper, instantiateSignature); - const stringIndexType = instantiateType(getIndexTypeOfType(type.target, IndexKind.String), type.mapper); - const numberIndexType = instantiateType(getIndexTypeOfType(type.target, IndexKind.Number), type.mapper); - setObjectTypeMembers(type, members, callSignatures, constructSignatures, stringIndexType, numberIndexType); + const stringIndexInfo = instantiateIndexInfo(getIndexInfoOfType(type.target, IndexKind.String), type.mapper); + const numberIndexInfo = instantiateIndexInfo(getIndexInfoOfType(type.target, IndexKind.Number), type.mapper); + setObjectTypeMembers(type, members, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo); } else if (symbol.flags & SymbolFlags.TypeLiteral) { const members = symbol.members; const callSignatures = getSignaturesOfSymbol(members["__call"]); const constructSignatures = getSignaturesOfSymbol(members["__new"]); - const stringIndexType = getIndexTypeOfSymbol(symbol, IndexKind.String); - const numberIndexType = getIndexTypeOfSymbol(symbol, IndexKind.Number); - setObjectTypeMembers(type, members, callSignatures, constructSignatures, stringIndexType, numberIndexType); + const stringIndexInfo = getIndexInfoOfSymbol(symbol, IndexKind.String); + const numberIndexInfo = getIndexInfoOfSymbol(symbol, IndexKind.Number); + setObjectTypeMembers(type, members, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo); } else { // Combinations of function, class, enum and module @@ -3605,8 +3614,8 @@ namespace ts { addInheritedMembers(members, getPropertiesOfObjectType(baseConstructorType)); } } - const numberIndexType = (symbol.flags & SymbolFlags.Enum) ? stringType : undefined; - setObjectTypeMembers(type, members, emptyArray, constructSignatures, undefined, numberIndexType); + const numberIndexInfo = symbol.flags & SymbolFlags.Enum ? enumNumberIndexInfo : undefined; + setObjectTypeMembers(type, members, emptyArray, constructSignatures, undefined, numberIndexInfo); // We resolve the members before computing the signatures because a signature may use // typeof with a qualified name expression that circularly references the type we are // in the process of resolving (see issue #6072). The temporarily empty signature list @@ -3825,13 +3834,25 @@ namespace ts { function getSignaturesOfType(type: Type, kind: SignatureKind): Signature[] { return getSignaturesOfStructuredType(getApparentType(type), kind); } - function getIndexTypeOfStructuredType(type: Type, kind: IndexKind): Type { + + function getIndexInfoOfStructuredType(type: Type, kind: IndexKind): IndexInfo { if (type.flags & TypeFlags.StructuredType) { const resolved = resolveStructuredTypeMembers(type); - return kind === IndexKind.String ? resolved.stringIndexType : resolved.numberIndexType; + return kind === IndexKind.String ? resolved.stringIndexInfo : resolved.numberIndexInfo; } } + function getIndexTypeOfStructuredType(type: Type, kind: IndexKind): Type { + const info = getIndexInfoOfStructuredType(type, kind); + return info && info.type; + } + + // Return the indexing info of the given kind in the given type. Creates synthetic union index types when necessary and + // maps primitive types and type parameters are to their apparent types. + function getIndexInfoOfType(type: Type, kind: IndexKind): IndexInfo { + return getIndexInfoOfStructuredType(getApparentType(type), kind); + } + // Return the index type of the given kind in the given type. Creates synthetic union index types when necessary and // maps primitive types and type parameters are to their apparent types. function getIndexTypeOfType(type: Type, kind: IndexKind): Type { @@ -4104,11 +4125,17 @@ namespace ts { return undefined; } - function getIndexTypeOfSymbol(symbol: Symbol, kind: IndexKind): Type { + function createIndexInfo(type: Type, isReadonly: boolean): IndexInfo { + return { type, isReadonly }; + } + + function getIndexInfoOfSymbol(symbol: Symbol, kind: IndexKind): IndexInfo { const declaration = getIndexDeclarationOfSymbol(symbol, kind); - return declaration - ? declaration.type ? getTypeFromTypeNode(declaration.type) : anyType - : undefined; + if (declaration) { + return createIndexInfo(declaration.type ? getTypeFromTypeNode(declaration.type) : anyType, + (declaration.flags & NodeFlags.Readonly) !== 0); + } + return undefined; } function getConstraintDeclaration(type: TypeParameter) { @@ -4845,6 +4872,10 @@ namespace ts { return type; } + function instantiateIndexInfo(info: IndexInfo, mapper: TypeMapper): IndexInfo { + return info && createIndexInfo(instantiateType(info.type, mapper), info.isReadonly); + } + // Returns true if the given expression contains (at any level of nesting) a function or arrow expression // that is subject to contextual typing. function isContextSensitive(node: Expression | MethodDeclaration | ObjectLiteralElement): boolean { @@ -5251,7 +5282,7 @@ namespace ts { if (type.flags & TypeFlags.ObjectType) { const resolved = resolveStructuredTypeMembers(type); if (relation === assignableRelation && (type === globalObjectType || resolved.properties.length === 0) || - resolved.stringIndexType || resolved.numberIndexType || getPropertyOfType(type, name)) { + resolved.stringIndexInfo || resolved.numberIndexInfo || getPropertyOfType(type, name)) { return true; } } @@ -6020,8 +6051,8 @@ namespace ts { regularType.properties = (type).properties; regularType.callSignatures = (type).callSignatures; regularType.constructSignatures = (type).constructSignatures; - regularType.stringIndexType = (type).stringIndexType; - regularType.numberIndexType = (type).numberIndexType; + regularType.stringIndexInfo = (type).stringIndexInfo; + regularType.numberIndexInfo = (type).numberIndexInfo; (type).regularType = regularType; } return regularType; @@ -6046,11 +6077,11 @@ namespace ts { } members[p.name] = p; }); - let stringIndexType = getIndexTypeOfType(type, IndexKind.String); - let numberIndexType = getIndexTypeOfType(type, IndexKind.Number); - if (stringIndexType) stringIndexType = getWidenedType(stringIndexType); - if (numberIndexType) numberIndexType = getWidenedType(numberIndexType); - return createAnonymousType(type.symbol, members, emptyArray, emptyArray, stringIndexType, numberIndexType); + const stringIndexInfo = getIndexInfoOfType(type, IndexKind.String); + const numberIndexInfo = getIndexInfoOfType(type, IndexKind.Number); + return createAnonymousType(type.symbol, members, emptyArray, emptyArray, + stringIndexInfo && createIndexInfo(getWidenedType(stringIndexInfo.type), stringIndexInfo.isReadonly), + numberIndexInfo && createIndexInfo(getWidenedType(numberIndexInfo.type), numberIndexInfo.isReadonly)); } function getWidenedType(type: Type): Type { @@ -7428,7 +7459,7 @@ namespace ts { // Return true if the given contextual type provides an index signature of the given kind function contextualTypeHasIndexSignature(type: Type, kind: IndexKind): boolean { - return !!(type.flags & TypeFlags.Union ? forEach((type).types, t => getIndexTypeOfStructuredType(t, kind)) : getIndexTypeOfStructuredType(type, kind)); + return !!(type.flags & TypeFlags.Union ? forEach((type).types, t => getIndexInfoOfStructuredType(t, kind)) : getIndexInfoOfStructuredType(type, kind)); } // In an object literal contextually typed by a type T, the contextual type of a property assignment is the type of @@ -7923,9 +7954,9 @@ namespace ts { } } - const stringIndexType = getIndexType(IndexKind.String); - const numberIndexType = getIndexType(IndexKind.Number); - const result = createAnonymousType(node.symbol, propertiesTable, emptyArray, emptyArray, stringIndexType, numberIndexType); + const stringIndexInfo = getIndexInfo(IndexKind.String); + const numberIndexInfo = getIndexInfo(IndexKind.Number); + const result = createAnonymousType(node.symbol, propertiesTable, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo); const freshObjectLiteralFlag = compilerOptions.suppressExcessPropertyErrors ? 0 : TypeFlags.FreshObjectLiteral; result.flags |= TypeFlags.ObjectLiteral | TypeFlags.ContainsObjectLiteral | freshObjectLiteralFlag | (typeFlags & TypeFlags.PropagatingFlags) | (patternWithComputedProperties ? TypeFlags.ObjectLiteralPatternWithComputedProperties : 0); if (inDestructuringPattern) { @@ -7933,7 +7964,7 @@ namespace ts { } return result; - function getIndexType(kind: IndexKind) { + function getIndexInfo(kind: IndexKind) { if (contextualType && contextualTypeHasIndexSignature(contextualType, kind)) { const propTypes: Type[] = []; for (let i = 0; i < propertiesArray.length; i++) { @@ -7949,9 +7980,9 @@ namespace ts { } } } - const result = propTypes.length ? getUnionType(propTypes) : undefinedType; - typeFlags |= result.flags; - return result; + const unionType = propTypes.length ? getUnionType(propTypes) : undefinedType; + typeFlags |= unionType.flags; + return createIndexInfo(unionType, /*isReadonly*/ false); } return undefined; } @@ -8321,7 +8352,7 @@ namespace ts { return links.resolvedJsxType = getTypeOfSymbol(sym); } else if (links.jsxFlags & JsxFlags.IntrinsicIndexedElement) { - return links.resolvedJsxType = getIndexTypeOfSymbol(sym, IndexKind.String); + return links.resolvedJsxType = getIndexInfoOfSymbol(sym, IndexKind.String).type; } else { // Resolution failed, so we don't know @@ -8956,7 +8987,7 @@ namespace ts { if (type.flags & TypeFlags.ObjectType) { const resolved = resolveStructuredTypeMembers(type); if (resolved.callSignatures.length === 1 && resolved.constructSignatures.length === 0 && - resolved.properties.length === 0 && !resolved.stringIndexType && !resolved.numberIndexType) { + resolved.properties.length === 0 && !resolved.stringIndexInfo && !resolved.numberIndexInfo) { return resolved.callSignatures[0]; } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index f6216664d2e..1ccb9e4067c 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2175,8 +2175,8 @@ namespace ts { declaredProperties: Symbol[]; // Declared members declaredCallSignatures: Signature[]; // Declared call signatures declaredConstructSignatures: Signature[]; // Declared construct signatures - declaredStringIndexType: Type; // Declared string index type - declaredNumberIndexType: Type; // Declared numeric index type + declaredStringIndexInfo: IndexInfo; // Declared string indexing info + declaredNumberIndexInfo: IndexInfo; // Declared numeric indexing info } // Type references (TypeFlags.Reference). When a class or interface has type parameters or @@ -2228,8 +2228,8 @@ namespace ts { properties: Symbol[]; // Properties callSignatures: Signature[]; // Call signatures of type constructSignatures: Signature[]; // Construct signatures of type - stringIndexType?: Type; // String index type - numberIndexType?: Type; // Numeric index type + stringIndexInfo?: IndexInfo; // String indexing info + numberIndexInfo?: IndexInfo; // Numeric indexing info } /* @internal */ @@ -2292,6 +2292,11 @@ namespace ts { Number, } + export interface IndexInfo { + type: Type; + isReadonly: boolean; + } + /* @internal */ export interface TypeMapper { (t: TypeParameter): Type;