Initial implementation of 'readonly' type operator

This commit is contained in:
Anders Hejlsberg
2017-07-11 07:00:03 -10:00
parent ff5d245dcb
commit be269dbe33
4 changed files with 116 additions and 34 deletions
+80 -15
View File
@@ -2490,13 +2490,18 @@ namespace ts {
if (type.flags & TypeFlags.Index) {
const indexedType = (<IndexType>type).type;
const indexTypeNode = typeToTypeNodeHelper(indexedType, context);
return createTypeOperatorNode(indexTypeNode);
return createTypeOperatorNode(SyntaxKind.KeyOfKeyword, indexTypeNode);
}
if (type.flags & TypeFlags.IndexedAccess) {
const objectTypeNode = typeToTypeNodeHelper((<IndexedAccessType>type).objectType, context);
const indexTypeNode = typeToTypeNodeHelper((<IndexedAccessType>type).indexType, context);
return createIndexedAccessTypeNode(objectTypeNode, indexTypeNode);
}
if (type.flags & TypeFlags.Readonly || objectFlags & ObjectFlags.Readonly) {
const readonlyType = (<ReadonlyTypeVariable | ReadonlyObjectType>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(<LiteralType>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((<IndexType>type).type, TypeFormatFlags.InElementType);
writeType((<IndexType | ReadonlyTypeVariable>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<Symbol>() 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 && (<TypeOperatorNode>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 (!(<ResolvedType>type).members) {
if (type.flags & TypeFlags.Object) {
if ((<ObjectType>type).objectFlags & ObjectFlags.Reference) {
const objectFlags = (<ObjectType>type).objectFlags;
if (objectFlags & ObjectFlags.Reference) {
resolveTypeReferenceMembers(<TypeReference>type);
}
else if ((<ObjectType>type).objectFlags & ObjectFlags.ClassOrInterface) {
else if (objectFlags & ObjectFlags.ClassOrInterface) {
resolveClassOrInterfaceMembers(<InterfaceType>type);
}
else if ((<ObjectType>type).objectFlags & ObjectFlags.Anonymous) {
else if (objectFlags & ObjectFlags.Anonymous) {
resolveAnonymousTypeMembers(<AnonymousType>type);
}
else if ((<MappedType>type).objectFlags & ObjectFlags.Mapped) {
else if (objectFlags & ObjectFlags.Readonly) {
resolveReadonlyTypeMembers(<ReadonlyObjectType>type);
}
else if (objectFlags & ObjectFlags.Mapped) {
resolveMappedTypeMembers(<MappedType>type);
}
}
@@ -5875,7 +5901,8 @@ namespace ts {
function getConstraintOfType(type: TypeVariable | UnionOrIntersectionType): Type {
return type.flags & TypeFlags.TypeParameter ? getConstraintOfTypeParameter(<TypeParameter>type) :
type.flags & TypeFlags.IndexedAccess ? getConstraintOfIndexedAccess(<IndexedAccessType>type) :
getBaseConstraintOfType(type);
type.flags & TypeFlags.Readonly ? getConstraintOfReadonlyTypeVariable(<ReadonlyTypeVariable>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(<TypeVariable | UnionOrIntersectionType>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((<ReadonlyTypeVariable>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 = <ReadonlyObjectType>createObjectType(ObjectFlags.Readonly, type.symbol);
result.type = type;
return result;
}
function createReadonlyTypeVariable(type: TypeVariable): ReadonlyTypeVariable {
const result = <ReadonlyTypeVariable>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(<ObjectType>type) :
type.flags & TypeFlags.Union ? getUnionType(sameMap((<UnionType>type).types, getReadonlyType)) :
type.flags & TypeFlags.Intersection ? getIntersectionType(sameMap((<IntersectionType>type).types, getReadonlyType)) :
createReadonlyTypeVariable(<TypeVariable>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;
+3 -3
View File
@@ -627,15 +627,15 @@ namespace ts {
return <ThisTypeNode>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) {
+5 -3
View File
@@ -2653,7 +2653,7 @@ namespace ts {
return type;
}
function parseTypeOperator(operator: SyntaxKind.KeyOfKeyword) {
function parseTypeOperator(operator: SyntaxKind.KeyOfKeyword | SyntaxKind.ReadonlyKeyword) {
const node = <TypeOperatorNode>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();
}
+28 -13
View File
@@ -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