diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7358eb8b771..8d7eb17935f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2490,13 +2490,18 @@ namespace ts { if (type.flags & TypeFlags.Index) { const indexedType = (type).type; const indexTypeNode = typeToTypeNodeHelper(indexedType, context); - return createTypeOperatorNode(indexTypeNode); + return createTypeOperatorNode(SyntaxKind.KeyOfKeyword, indexTypeNode); } if (type.flags & TypeFlags.IndexedAccess) { const objectTypeNode = typeToTypeNodeHelper((type).objectType, context); const indexTypeNode = typeToTypeNodeHelper((type).indexType, context); return createIndexedAccessTypeNode(objectTypeNode, indexTypeNode); } + if (type.flags & TypeFlags.Readonly || objectFlags & ObjectFlags.Readonly) { + const readonlyType = (type).type; + const readonlyTypeNode = typeToTypeNodeHelper(readonlyType, context); + return createTypeOperatorNode(SyntaxKind.ReadonlyKeyword, readonlyTypeNode); + } Debug.fail("Should be unreachable."); @@ -3247,13 +3252,13 @@ namespace ts { else if (type.flags & TypeFlags.StringOrNumberLiteral) { writer.writeStringLiteral(literalTypeToString(type)); } - else if (type.flags & TypeFlags.Index) { + else if (type.flags & (TypeFlags.Index | TypeFlags.Readonly) || getObjectFlags(type) & ObjectFlags.Readonly) { if (flags & TypeFormatFlags.InElementType) { writePunctuation(writer, SyntaxKind.OpenParenToken); } - writer.writeKeyword("keyof"); + writer.writeKeyword(type.flags & TypeFlags.Index ? "keyof" : "readonly"); writeSpace(writer); - writeType((type).type, TypeFormatFlags.InElementType); + writeType((type).type, TypeFormatFlags.InElementType); if (flags & TypeFormatFlags.InElementType) { writePunctuation(writer, SyntaxKind.CloseParenToken); } @@ -4676,7 +4681,7 @@ namespace ts { if (!popTypeResolution()) { type = reportCircularityError(symbol); } - links.type = type; + links.type = getCheckFlags(symbol) & CheckFlags.ReadonlyType ? getReadonlyType(type) : type; } } return links.type; @@ -5666,6 +5671,23 @@ namespace ts { } } + function createReadonlyIndexInfo(indexInfo: IndexInfo): IndexInfo { + return indexInfo && !indexInfo.isReadonly ? createIndexInfo(indexInfo.type, true, indexInfo.declaration) : indexInfo; + } + + function resolveReadonlyTypeMembers(type: ReadonlyObjectType) { + const target = type.type; + const members = createMap() as SymbolTable; + for (const symbol of getPropertiesOfObjectType(target)) { + members.set(symbol.name, instantiateSymbol(symbol, identityMapper, /*readonly*/ true)); + } + const callSignatures = getSignaturesOfType(target, SignatureKind.Call); + const constructSignatures = getSignaturesOfType(target, SignatureKind.Construct); + const stringIndexInfo = createReadonlyIndexInfo(getIndexInfoOfType(target, IndexKind.String)); + const numberIndexInfo = createReadonlyIndexInfo(getIndexInfoOfType(target, IndexKind.Number)); + setStructuredTypeMembers(type, members, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo); + } + /** Resolve the members of a mapped type { [P in K]: T } */ function resolveMappedTypeMembers(type: MappedType) { const members: SymbolTable = createSymbolTable(); @@ -5747,7 +5769,7 @@ namespace ts { function getModifiersTypeFromMappedType(type: MappedType) { if (!type.modifiersType) { const constraintDeclaration = type.declaration.typeParameter.constraint; - if (constraintDeclaration.kind === SyntaxKind.TypeOperator) { + if (constraintDeclaration.kind === SyntaxKind.TypeOperator && (constraintDeclaration).operator === SyntaxKind.KeyOfKeyword) { // If the constraint declaration is a 'keyof T' node, the modifiers type is T. We check // AST nodes here because, when T is a non-generic type, the logic below eagerly resolves // 'keyof T' to a literal union type and we can't recover T from that type. @@ -5777,16 +5799,20 @@ namespace ts { function resolveStructuredTypeMembers(type: StructuredType): ResolvedType { if (!(type).members) { if (type.flags & TypeFlags.Object) { - if ((type).objectFlags & ObjectFlags.Reference) { + const objectFlags = (type).objectFlags; + if (objectFlags & ObjectFlags.Reference) { resolveTypeReferenceMembers(type); } - else if ((type).objectFlags & ObjectFlags.ClassOrInterface) { + else if (objectFlags & ObjectFlags.ClassOrInterface) { resolveClassOrInterfaceMembers(type); } - else if ((type).objectFlags & ObjectFlags.Anonymous) { + else if (objectFlags & ObjectFlags.Anonymous) { resolveAnonymousTypeMembers(type); } - else if ((type).objectFlags & ObjectFlags.Mapped) { + else if (objectFlags & ObjectFlags.Readonly) { + resolveReadonlyTypeMembers(type); + } + else if (objectFlags & ObjectFlags.Mapped) { resolveMappedTypeMembers(type); } } @@ -5875,7 +5901,8 @@ namespace ts { function getConstraintOfType(type: TypeVariable | UnionOrIntersectionType): Type { return type.flags & TypeFlags.TypeParameter ? getConstraintOfTypeParameter(type) : type.flags & TypeFlags.IndexedAccess ? getConstraintOfIndexedAccess(type) : - getBaseConstraintOfType(type); + type.flags & TypeFlags.Readonly ? getConstraintOfReadonlyTypeVariable(type) : + getBaseConstraintOfType(type); } function getConstraintOfTypeParameter(typeParameter: TypeParameter): Type { @@ -5888,6 +5915,11 @@ namespace ts { return baseObjectType || baseIndexType ? getIndexedAccessType(baseObjectType || type.objectType, baseIndexType || type.indexType) : undefined; } + function getConstraintOfReadonlyTypeVariable(type: ReadonlyTypeVariable) { + const baseType = getBaseConstraintOfType(type.type); + return baseType ? getReadonlyType(baseType) : undefined; + } + function getBaseConstraintOfType(type: Type): Type { if (type.flags & (TypeFlags.TypeVariable | TypeFlags.UnionOrIntersection)) { const constraint = getResolvedBaseConstraint(type); @@ -5959,6 +5991,10 @@ namespace ts { const baseIndexedAccess = baseObjectType && baseIndexType ? getIndexedAccessType(baseObjectType, baseIndexType) : undefined; return baseIndexedAccess && baseIndexedAccess !== unknownType ? getBaseConstraint(baseIndexedAccess) : undefined; } + if (t.flags & TypeFlags.Readonly) { + const baseConstraint = getBaseConstraint((t).type); + return baseConstraint ? getReadonlyType(baseConstraint) : undefined; + } return t; } } @@ -7478,10 +7514,36 @@ namespace ts { return indexType !== neverType ? indexType : stringType; } + function createReadonlyObjectType(type: ObjectType): ReadonlyObjectType { + const result = createObjectType(ObjectFlags.Readonly, type.symbol); + result.type = type; + return result; + } + + function createReadonlyTypeVariable(type: TypeVariable): ReadonlyTypeVariable { + const result = createType(TypeFlags.Readonly); + result.type = type; + return result; + } + + function getReadonlyType(type: Type): Type { + if (!(type.flags & TypeFlags.HasReadonlyForm)) { + return type; + } + if (!type.readonlyType) { + type.readonlyType = type.flags & TypeFlags.Object ? createReadonlyObjectType(type) : + type.flags & TypeFlags.Union ? getUnionType(sameMap((type).types, getReadonlyType)) : + type.flags & TypeFlags.Intersection ? getIntersectionType(sameMap((type).types, getReadonlyType)) : + createReadonlyTypeVariable(type); + } + return type.readonlyType; + } + function getTypeFromTypeOperatorNode(node: TypeOperatorNode) { const links = getNodeLinks(node); if (!links.resolvedType) { - links.resolvedType = getIndexType(getTypeFromTypeNode(node.type)); + const type = getTypeFromTypeNode(node.type); + links.resolvedType = node.operator === SyntaxKind.KeyOfKeyword ? getIndexType(type) : getReadonlyType(type); } return links.resolvedType; } @@ -8006,6 +8068,8 @@ namespace ts { } function combineTypeMappers(mapper1: TypeMapper, mapper2: TypeMapper): TypeMapper { + if (mapper1 === identityMapper) return mapper2; + if (mapper2 === identityMapper) return mapper1; const mapper: TypeMapper = t => instantiateType(mapper1(t), mapper2); mapper.mappedTypes = concatenate(mapper1.mappedTypes, mapper2.mappedTypes); return mapper; @@ -8068,8 +8132,9 @@ namespace ts { return result; } - function instantiateSymbol(symbol: Symbol, mapper: TypeMapper): Symbol { - if (getCheckFlags(symbol) & CheckFlags.Instantiated) { + function instantiateSymbol(symbol: Symbol, mapper: TypeMapper, readonly?: boolean): Symbol { + const checkFlags = getCheckFlags(symbol); + if (checkFlags & CheckFlags.Instantiated) { const links = getSymbolLinks(symbol); // If symbol being instantiated is itself a instantiation, fetch the original target and combine the // type mappers. This ensures that original type identities are properly preserved and that aliases @@ -8080,7 +8145,7 @@ namespace ts { // Keep the flags from the symbol we're instantiating. Mark that is instantiated, and // also transient so that we can just store data on it directly. const result = createSymbol(symbol.flags, symbol.name); - result.checkFlags = CheckFlags.Instantiated; + result.checkFlags = CheckFlags.Instantiated | (readonly || checkFlags & CheckFlags.ReadonlyType ? CheckFlags.Readonly | CheckFlags.ReadonlyType : 0); result.declarations = symbol.declarations; result.parent = symbol.parent; result.target = symbol; diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index c51ebe9fd58..5eb6fddf2a6 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -627,15 +627,15 @@ namespace ts { return createSynthesizedNode(SyntaxKind.ThisType); } - export function createTypeOperatorNode(type: TypeNode) { + export function createTypeOperatorNode(operator: SyntaxKind.KeyOfKeyword | SyntaxKind.ReadonlyKeyword, type: TypeNode) { const node = createSynthesizedNode(SyntaxKind.TypeOperator) as TypeOperatorNode; - node.operator = SyntaxKind.KeyOfKeyword; + node.operator = operator; node.type = parenthesizeElementTypeMember(type); return node; } export function updateTypeOperatorNode(node: TypeOperatorNode, type: TypeNode) { - return node.type !== type ? updateNode(createTypeOperatorNode(type), node) : node; + return node.type !== type ? updateNode(createTypeOperatorNode(node.operator, type), node) : node; } export function createIndexedAccessTypeNode(objectType: TypeNode, indexType: TypeNode) { diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 4a979fb3e6f..cb64d662c92 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -2653,7 +2653,7 @@ namespace ts { return type; } - function parseTypeOperator(operator: SyntaxKind.KeyOfKeyword) { + function parseTypeOperator(operator: SyntaxKind.KeyOfKeyword | SyntaxKind.ReadonlyKeyword) { const node = createNode(SyntaxKind.TypeOperator); parseExpected(operator); node.operator = operator; @@ -2662,9 +2662,11 @@ namespace ts { } function parseTypeOperatorOrHigher(): TypeNode { - switch (token()) { + const operator = token(); + switch (operator) { case SyntaxKind.KeyOfKeyword: - return parseTypeOperator(SyntaxKind.KeyOfKeyword); + case SyntaxKind.ReadonlyKeyword: + return parseTypeOperator(operator); } return parseArrayTypeOrHigher(); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 63b50d58e4a..c66c8e1932c 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -986,7 +986,7 @@ namespace ts { export interface TypeOperatorNode extends TypeNode { kind: SyntaxKind.TypeOperator; - operator: SyntaxKind.KeyOfKeyword; + operator: SyntaxKind.KeyOfKeyword | SyntaxKind.ReadonlyKeyword; type: TypeNode; } @@ -3023,6 +3023,7 @@ namespace ts { ContainsProtected = 1 << 7, // Synthetic property with protected constituent(s) ContainsPrivate = 1 << 8, // Synthetic property with private constituent(s) ContainsStatic = 1 << 9, // Synthetic property with static constituent(s) + ReadonlyType = 1 << 10, // Obtain readonly form of type Synthetic = SyntheticProperty | SyntheticMethod } @@ -3157,17 +3158,18 @@ namespace ts { Intersection = 1 << 17, // Intersection (T & U) Index = 1 << 18, // keyof T IndexedAccess = 1 << 19, // T[K] + Readonly = 1 << 20, // readonly T /* @internal */ - FreshLiteral = 1 << 20, // Fresh literal type + FreshLiteral = 1 << 21, // Fresh literal type /* @internal */ - ContainsWideningType = 1 << 21, // Type is or contains undefined or null widening type + ContainsWideningType = 1 << 22, // Type is or contains undefined or null widening type /* @internal */ - ContainsObjectLiteral = 1 << 22, // Type is or contains object literal type + ContainsObjectLiteral = 1 << 23, // Type is or contains object literal type /* @internal */ - ContainsAnyFunctionType = 1 << 23, // Type is or contains object literal type - NonPrimitive = 1 << 24, // intrinsic object type + ContainsAnyFunctionType = 1 << 24, // Type is or contains object literal type + NonPrimitive = 1 << 25, // intrinsic object type /* @internal */ - JsxAttributes = 1 << 25, // Jsx attributes type + JsxAttributes = 1 << 26, // Jsx attributes type /* @internal */ Nullable = Undefined | Null, @@ -3186,12 +3188,13 @@ namespace ts { EnumLike = Enum | EnumLiteral, UnionOrIntersection = Union | Intersection, StructuredType = Object | Union | Intersection, - StructuredOrTypeVariable = StructuredType | TypeParameter | Index | IndexedAccess, - TypeVariable = TypeParameter | IndexedAccess, + TypeVariable = TypeParameter | IndexedAccess | Readonly, + StructuredOrTypeVariable = StructuredType | TypeVariable | Index, + HasReadonlyForm = Object | Union | Intersection | TypeParameter | IndexedAccess, // 'Narrowable' types are types where narrowing actually narrows. // This *should* be every type other than null, undefined, void, and never - Narrowable = Any | StructuredType | TypeParameter | Index | IndexedAccess | StringLike | NumberLike | BooleanLike | ESSymbol | NonPrimitive, + Narrowable = Any | StructuredOrTypeVariable | StringLike | NumberLike | BooleanLike | ESSymbol | NonPrimitive, NotUnionOrUnit = Any | ESSymbol | Object | NonPrimitive, /* @internal */ RequiresWidening = ContainsWideningType | ContainsObjectLiteral, @@ -3210,6 +3213,7 @@ namespace ts { pattern?: DestructuringPattern; // Destructuring pattern represented by type (if any) aliasSymbol?: Symbol; // Alias associated with type aliasTypeArguments?: Type[]; // Alias type arguments (if any) + readonlyType?: Type; // Readonly instance of type } /* @internal */ @@ -3245,9 +3249,10 @@ namespace ts { Tuple = 1 << 3, // Synthesized generic tuple type Anonymous = 1 << 4, // Anonymous Mapped = 1 << 5, // Mapped - Instantiated = 1 << 6, // Instantiated anonymous or mapped type - ObjectLiteral = 1 << 7, // Originates in an object literal - EvolvingArray = 1 << 8, // Evolving array type + Readonly = 1 << 6, // Readonly + Instantiated = 1 << 7, // Instantiated anonymous or mapped type + ObjectLiteral = 1 << 8, // Originates in an object literal + EvolvingArray = 1 << 9, // Evolving array type ObjectLiteralPatternWithComputedProperties = 1 << 9, // Object literal pattern with computed properties ClassOrInterface = Class | Interface } @@ -3341,6 +3346,16 @@ namespace ts { mapper?: TypeMapper; // Instantiation mapper } + // Readonly object type (ObjectFlags.Readonly) + export interface ReadonlyObjectType extends ObjectType { + type: ObjectType; + } + + // Readonly type variable (TypeFlags.Readonly) + export interface ReadonlyTypeVariable extends Type { + type: TypeVariable; + } + export interface EvolvingArrayType extends ObjectType { elementType: Type; // Element expressions of evolving array type finalArrayType?: Type; // Final array type of evolving array type