-
Notifications
You must be signed in to change notification settings - Fork 12.8k
do not add 'this' type for classes that considered safe (use 'this' only in property\element access expressions) #7474
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -825,7 +825,8 @@ namespace ts { | |
// No static member is present. | ||
// Check if we're in an instance method and look for a relevant instance member. | ||
if (location === container && !(location.flags & NodeFlags.Static)) { | ||
const instanceType = (<InterfaceType>getDeclaredTypeOfSymbol(classSymbol)).thisType; | ||
const declaredType = <InterfaceType>getDeclaredTypeOfSymbol(classSymbol); | ||
const instanceType = declaredType.thisType || declaredType; | ||
if (getPropertyOfType(instanceType, name)) { | ||
error(errorLocation, Diagnostics.Cannot_find_name_0_Did_you_mean_the_instance_member_this_0, typeof nameArg === "string" ? nameArg : declarationNameToString(nameArg)); | ||
return true; | ||
|
@@ -3328,25 +3329,62 @@ namespace ts { | |
} | ||
} | ||
|
||
// Returns true if the interface given by the symbol is free of "this" references. Specifically, the result is | ||
// true if the interface itself contains no references to "this" in its body, if all base types are interfaces, | ||
// and if none of the base interfaces have a "this" type. | ||
function isIndependentInterface(symbol: Symbol): boolean { | ||
function isIndependentBaseInterfaceList(nodes: NodeArray<ExpressionWithTypeArguments>): boolean { | ||
if (!nodes) { | ||
return true; | ||
} | ||
for (const node of nodes) { | ||
if (!isIndependentHeritageClauseElement(node, /*classSymbol*/ undefined)) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
|
||
function isIndependentHeritageClauseElement(node: ExpressionWithTypeArguments, classSymbol: Symbol): boolean { | ||
if (!node) { | ||
return true; | ||
} | ||
if (isSupportedExpressionWithTypeArguments(node)) { | ||
const baseSymbol = resolveEntityName(node.expression, SymbolFlags.Type, /*ignoreErrors*/ true); | ||
if (!baseSymbol || baseSymbol === unknownSymbol) { | ||
// pessimistically asssume that base type is not independent | ||
return false; | ||
} | ||
const baseType = <InterfaceType>getDeclaredTypeOfSymbol(baseSymbol); | ||
if (baseType.thisType) { | ||
return false; | ||
} | ||
} | ||
else if (classSymbol) { | ||
Debug.assert((classSymbol.flags & SymbolFlags.Class) !== 0); | ||
const baseTypes = getBaseTypes(<InterfaceType>getDeclaredTypeOfClassOrInterface(classSymbol)); | ||
if (baseTypes.length) { | ||
return (<InterfaceType>baseTypes[0]).thisType === undefined; | ||
} | ||
} | ||
return true; | ||
} | ||
|
||
// Returns true if the class\interface given by the symbol is free of "this" references. Specifically, the result is | ||
// true if the class\interface itself contains no references to "this" in its body, if all base types are classes\interfaces, | ||
// and if none of the base classes\interfaces have a "this" type. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For interfaces, what if the derived types (if any) have references to |
||
function isIndependentClassOrInterface(symbol: Symbol): boolean { | ||
for (const declaration of symbol.declarations) { | ||
if (declaration.flags & NodeFlags.UsesThisTypeOrReference) { | ||
return false; | ||
} | ||
if (declaration.kind === SyntaxKind.InterfaceDeclaration) { | ||
if (declaration.flags & NodeFlags.ContainsThis) { | ||
if (!isIndependentBaseInterfaceList(getInterfaceBaseTypeNodes(<InterfaceDeclaration>declaration))) { | ||
return false; | ||
} | ||
const baseTypeNodes = getInterfaceBaseTypeNodes(<InterfaceDeclaration>declaration); | ||
if (baseTypeNodes) { | ||
for (const node of baseTypeNodes) { | ||
if (isSupportedExpressionWithTypeArguments(node)) { | ||
const baseSymbol = resolveEntityName(node.expression, SymbolFlags.Type, /*ignoreErrors*/ true); | ||
if (!baseSymbol || !(baseSymbol.flags & SymbolFlags.Interface) || getDeclaredTypeOfClassOrInterface(baseSymbol).thisType) { | ||
return false; | ||
} | ||
} | ||
} | ||
} | ||
else if (isClassLike(declaration)) { | ||
const isIndependent = isIndependentHeritageClauseElement(getClassExtendsHeritageClauseElement(declaration), symbol) && | ||
isIndependentBaseInterfaceList(getClassImplementsHeritageClauseElements(declaration)); | ||
|
||
if (!isIndependent) { | ||
return false; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would be easier to read as const isIndependentExtends = ...
const isIndependentImplements = ...
return isIndependentExtends && isIndependentImplements; (The passthrough There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. current version won't check There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. good point. Maybe just get rid of the |
||
} | ||
} | ||
} | ||
|
@@ -3365,7 +3403,9 @@ namespace ts { | |
// property types inferred from initializers and method return types inferred from return statements are very hard | ||
// to exhaustively analyze). We give interfaces a "this" type if we can't definitely determine that they are free of | ||
// "this" references. | ||
if (outerTypeParameters || localTypeParameters || kind === TypeFlags.Class || !isIndependentInterface(symbol)) { | ||
const isGeneric = outerTypeParameters || localTypeParameters; | ||
const isIndependent = isIndependentClassOrInterface(symbol); | ||
if (isGeneric || !isIndependent) { | ||
type.flags |= TypeFlags.Reference; | ||
type.typeParameters = concatenate(outerTypeParameters, localTypeParameters); | ||
type.outerTypeParameters = outerTypeParameters; | ||
|
@@ -3374,9 +3414,11 @@ namespace ts { | |
(<GenericType>type).instantiations[getTypeListId(type.typeParameters)] = <GenericType>type; | ||
(<GenericType>type).target = <GenericType>type; | ||
(<GenericType>type).typeArguments = type.typeParameters; | ||
type.thisType = <TypeParameter>createType(TypeFlags.TypeParameter | TypeFlags.ThisType); | ||
type.thisType.symbol = symbol; | ||
type.thisType.constraint = type; | ||
if (!isIndependent) { | ||
type.thisType = <TypeParameter>createType(TypeFlags.TypeParameter | TypeFlags.ThisType); | ||
type.thisType.symbol = symbol; | ||
type.thisType.constraint = type; | ||
} | ||
} | ||
} | ||
return <InterfaceType>links.declaredType; | ||
|
@@ -3576,13 +3618,18 @@ namespace ts { | |
} | ||
|
||
function getTypeWithThisArgument(type: ObjectType, thisArgument?: Type) { | ||
if (type.flags & TypeFlags.Reference) { | ||
if (hasAssociatedThisType(type)) { | ||
// instantiate type reference only if type uses thisType | ||
return createTypeReference((<TypeReference>type).target, | ||
concatenate((<TypeReference>type).typeArguments, [thisArgument || (<TypeReference>type).target.thisType])); | ||
} | ||
return type; | ||
} | ||
|
||
function hasAssociatedThisType(type: ObjectType): boolean { | ||
return type.flags & TypeFlags.Reference && !!(<TypeReference>type).target.thisType; | ||
} | ||
|
||
function resolveObjectTypeMembers(type: ObjectType, source: InterfaceTypeWithDeclaredMembers, typeParameters: TypeParameter[], typeArguments: Type[]) { | ||
let mapper = identityMapper; | ||
let members = source.symbol.members; | ||
|
@@ -3592,7 +3639,7 @@ namespace ts { | |
let numberIndexInfo = source.declaredNumberIndexInfo; | ||
if (!rangeEquals(typeParameters, typeArguments, 0, typeParameters.length)) { | ||
mapper = createTypeMapper(typeParameters, typeArguments); | ||
members = createInstantiatedSymbolTable(source.declaredProperties, mapper, /*mappingThisOnly*/ typeParameters.length === 1); | ||
members = createInstantiatedSymbolTable(source.declaredProperties, mapper, hasAssociatedThisType(type) && typeParameters.length === 1); | ||
callSignatures = instantiateList(source.declaredCallSignatures, mapper, instantiateSignature); | ||
constructSignatures = instantiateList(source.declaredConstructSignatures, mapper, instantiateSignature); | ||
stringIndexInfo = instantiateIndexInfo(source.declaredStringIndexInfo, mapper); | ||
|
@@ -3603,9 +3650,11 @@ namespace ts { | |
if (members === source.symbol.members) { | ||
members = createSymbolTable(source.declaredProperties); | ||
} | ||
const thisArgument = lastOrUndefined(typeArguments); | ||
const thisArgument = hasAssociatedThisType(type) ? lastOrUndefined(typeArguments) : undefined; | ||
for (const baseType of baseTypes) { | ||
const instantiatedBaseType = thisArgument ? getTypeWithThisArgument(instantiateType(baseType, mapper), thisArgument) : baseType; | ||
const instantiatedBaseType = thisArgument | ||
? getTypeWithThisArgument(instantiateType(baseType, mapper), thisArgument) | ||
: instantiateType(baseType, mapper); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why do we now need to instantiate the baseType with no this argument? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. because |
||
addInheritedMembers(members, getPropertiesOfObjectType(instantiatedBaseType)); | ||
callSignatures = concatenate(callSignatures, getSignaturesOfType(instantiatedBaseType, SignatureKind.Call)); | ||
constructSignatures = concatenate(constructSignatures, getSignaturesOfType(instantiatedBaseType, SignatureKind.Construct)); | ||
|
@@ -3622,7 +3671,7 @@ namespace ts { | |
|
||
function resolveTypeReferenceMembers(type: TypeReference): void { | ||
const source = resolveDeclaredMembers(type.target); | ||
const typeParameters = concatenate(source.typeParameters, [source.thisType]); | ||
const typeParameters = source.thisType ? concatenate(source.typeParameters, [source.thisType]) : source.typeParameters; | ||
const typeArguments = type.typeArguments && type.typeArguments.length === typeParameters.length ? | ||
type.typeArguments : concatenate(type.typeArguments, [type]); | ||
resolveObjectTypeMembers(type, source, typeParameters, typeArguments); | ||
|
@@ -4961,7 +5010,8 @@ namespace ts { | |
if (parent && (isClassLike(parent) || parent.kind === SyntaxKind.InterfaceDeclaration)) { | ||
if (!(container.flags & NodeFlags.Static) && | ||
(container.kind !== SyntaxKind.Constructor || isNodeDescendentOf(node, (<ConstructorDeclaration>container).body))) { | ||
return getDeclaredTypeOfClassOrInterface(getSymbolOfNode(parent)).thisType; | ||
const declaredType = getDeclaredTypeOfClassOrInterface(getSymbolOfNode(parent)); | ||
return declaredType.thisType || declaredType; | ||
} | ||
} | ||
error(node, Diagnostics.A_this_type_is_available_only_in_a_non_static_member_of_a_class_or_interface); | ||
|
@@ -5236,7 +5286,7 @@ namespace ts { | |
const declaration = <DeclarationWithTypeParameters>node; | ||
if (declaration.typeParameters) { | ||
for (const d of declaration.typeParameters) { | ||
if (contains(mappedTypes, getDeclaredTypeOfTypeParameter(d.symbol))) { | ||
if (contains(mappedTypes, getDeclaredTypeOfTypeParameter(getMergedSymbol(d.symbol)))) { | ||
return true; | ||
} | ||
} | ||
|
@@ -7637,7 +7687,11 @@ namespace ts { | |
|
||
if (isClassLike(container.parent)) { | ||
const symbol = getSymbolOfNode(container.parent); | ||
return container.flags & NodeFlags.Static ? getTypeOfSymbol(symbol) : (<InterfaceType>getDeclaredTypeOfSymbol(symbol)).thisType; | ||
if (container.flags & NodeFlags.Static) { | ||
return getTypeOfSymbol(symbol); | ||
} | ||
const declaredType = (<InterfaceType>getDeclaredTypeOfSymbol(symbol)); | ||
return declaredType.thisType || declaredType; | ||
} | ||
|
||
if (isInJavaScriptFile(node)) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be worth merging in Ron's
every
from the transforms branch and makingclassSymbol
optional so you can just say