diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8d7eb17935f..c7d72a1650a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -256,6 +256,7 @@ namespace ts { const neverType = createIntrinsicType(TypeFlags.Never, "never"); const silentNeverType = createIntrinsicType(TypeFlags.Never, "never"); const nonPrimitiveType = createIntrinsicType(TypeFlags.NonPrimitive, "object"); + const readonlyThisType = createIntrinsicType(TypeFlags.Any, "readonly"); const emptyObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); @@ -5675,11 +5676,37 @@ namespace ts { return indexInfo && !indexInfo.isReadonly ? createIndexInfo(indexInfo.type, true, indexInfo.declaration) : indexInfo; } + function hasReadonlyThisParameter(sig: SignatureDeclaration) { + const param = firstOrUndefined(sig.parameters); + return param && + param.name.kind === SyntaxKind.Identifier && + (param.name).text === "this" && + param.type && + param.type.kind === SyntaxKind.ReadonlyKeyword; + } + + function isReadonlyThisMethod(symbol: Symbol) { + if (symbol.flags & SymbolFlags.Method) { + for (let i = 0; i < symbol.declarations.length; i++) { + const node = symbol.declarations[i]; + if (node.kind === SyntaxKind.MethodDeclaration || node.kind === SyntaxKind.MethodSignature) { + if (!hasReadonlyThisParameter(node)) { + return false; + } + } + } + return true; + } + return false; + } + 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)); + if (!(symbol.flags & SymbolFlags.Method) || isReadonlyThisMethod(symbol)) { + members.set(symbol.name, instantiateSymbol(symbol, identityMapper, /*readonly*/ true)); + } } const callSignatures = getSignaturesOfType(target, SignatureKind.Call); const constructSignatures = getSignaturesOfType(target, SignatureKind.Construct); @@ -6546,7 +6573,8 @@ namespace ts { function getThisTypeOfSignature(signature: Signature): Type | undefined { if (signature.thisParameter) { - return getTypeOfSymbol(signature.thisParameter); + const type = getTypeOfSymbol(signature.thisParameter); + return type !== readonlyThisType ? type : undefined; } } @@ -7924,6 +7952,8 @@ namespace ts { return neverType; case SyntaxKind.ObjectKeyword: return nonPrimitiveType; + case SyntaxKind.ReadonlyKeyword: + return readonlyThisType; case SyntaxKind.ThisType: case SyntaxKind.ThisKeyword: return getTypeFromThisTypeNode(node); @@ -12410,9 +12440,9 @@ namespace ts { } if (isClassLike(container.parent)) { - const symbol = getSymbolOfNode(container.parent); - const type = hasModifier(container, ModifierFlags.Static) ? getTypeOfSymbol(symbol) : (getDeclaredTypeOfSymbol(symbol)).thisType; - return getFlowTypeOfReference(node, type); + const classSymbol = getSymbolOfNode(container.parent); + const type = hasModifier(container, ModifierFlags.Static) ? getTypeOfSymbol(classSymbol) : (getDeclaredTypeOfSymbol(classSymbol)).thisType; + return getFlowTypeOfReference(node, isReadonlyThisMethod(getSymbolOfNode(container)) ? getReadonlyType(type) : type); } if (isInJavaScriptFile(node)) { diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index cb64d662c92..056aa604052 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -2153,8 +2153,16 @@ namespace ts { } } - function parseParameterType(): TypeNode { + function nextTokenIsStartOfType() { + nextToken(); + return isStartOfType(); + } + + function parseParameterType(allowReadonly: boolean): TypeNode { if (parseOptional(SyntaxKind.ColonToken)) { + if (allowReadonly && token() === SyntaxKind.ReadonlyKeyword && !lookAhead(nextTokenIsStartOfType)) { + return parseTokenNode(); + } return parseType(); } @@ -2169,7 +2177,7 @@ namespace ts { const node = createNode(SyntaxKind.Parameter); if (token() === SyntaxKind.ThisKeyword) { node.name = createIdentifier(/*isIdentifier*/ true); - node.type = parseParameterType(); + node.type = parseParameterType(/*allowReadonly*/ true); return finishNode(node); } @@ -2193,7 +2201,7 @@ namespace ts { } node.questionToken = parseOptionalToken(SyntaxKind.QuestionToken); - node.type = parseParameterType(); + node.type = parseParameterType(/*allowReadonly*/ false); node.initializer = parseBindingElementInitializer(/*inParameter*/ true); // Do not check for initializers in an ambient context for parameters. This is not diff --git a/src/compiler/types.ts b/src/compiler/types.ts index c66c8e1932c..385086c7cf3 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3351,11 +3351,6 @@ namespace ts { 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 @@ -3425,6 +3420,11 @@ namespace ts { constraint?: Type; } + // Readonly type variable (TypeFlags.Readonly) + export interface ReadonlyTypeVariable extends TypeVariable { + type: TypeVariable; + } + // keyof T types (TypeFlags.Index) export interface IndexType extends Type { type: TypeVariable | UnionOrIntersectionType;