From 2fbab38323fc173195820851be4835d52f6c0894 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 12 Apr 2020 15:27:59 -0700 Subject: [PATCH 01/10] Use CFA to determine types of properties declared by this.xxx assignments --- src/compiler/checker.ts | 127 ++++++++++++++++++++++++++++------------ src/compiler/types.ts | 1 + 2 files changed, 89 insertions(+), 39 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5cab127293b72..f348e59d8d4e6 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7500,6 +7500,39 @@ namespace ts { return undefined; } + function isPropertyDeclaredInConstructor(symbol: Symbol) { + if (symbol.valueDeclaration && isBinaryExpression(symbol.valueDeclaration)) { + const links = getSymbolLinks(symbol); + if (links.isPropertyDeclaredInConstructor === undefined) { + links.isPropertyDeclaredInConstructor = !!getDeclaringConstructor(symbol) && every(symbol.declarations, declaration => + isBinaryExpression(declaration) && + getAssignmentDeclarationKind(declaration) === AssignmentDeclarationKind.ThisProperty && + (declaration.left.kind !== SyntaxKind.ElementAccessExpression || isStringOrNumericLiteralLike((declaration.left).argumentExpression)) && + !getAnnotatedTypeForAssignmentDeclaration(/*declaredType*/ undefined, declaration, symbol, declaration)); + } + return links.isPropertyDeclaredInConstructor; + } + return false; + } + + function getDeclaringConstructor(symbol: Symbol) { + for (const declaration of symbol.declarations) { + const container = getThisContainer(declaration, /*includeArrowFunctions*/ false); + if (container && container.kind === SyntaxKind.Constructor) { + return container; + } + } + } + + function getTypeOfPropertyDeclaredInConstructor(symbol: Symbol) { + const constructor = getDeclaringConstructor(symbol)!; + const reference = createPropertyAccess(createThis(), unescapeLeadingUnderscores(symbol.escapedName)); + reference.expression.parent = reference; + reference.parent = constructor; + reference.flowNode = constructor.returnFlowNode; + return getFlowTypeOfReference(reference, autoType, undefinedType); + } + function getWidenedTypeForAssignmentDeclaration(symbol: Symbol, resolvedSymbol?: Symbol) { // function/class/{} initializers are themselves containers, so they won't merge in the same way as other initializers const container = getAssignedExpandoInitializer(symbol.valueDeclaration); @@ -7511,52 +7544,58 @@ namespace ts { const containerObjectType = getJSContainerObjectType(symbol.valueDeclaration, symbol, container); return containerObjectType || getWidenedLiteralType(checkExpressionCached(container)); } + let type; let definedInConstructor = false; let definedInMethod = false; - let jsdocType: Type | undefined; - let types: Type[] | undefined; - for (const declaration of symbol.declarations) { - const expression = (isBinaryExpression(declaration) || isCallExpression(declaration)) ? declaration : - isAccessExpression(declaration) ? isBinaryExpression(declaration.parent) ? declaration.parent : declaration : - undefined; - if (!expression) { - continue; // Non-assignment declaration merged in (eg, an Identifier to mark the thing as a namespace) - skip over it and pull type info from elsewhere - } + if (isPropertyDeclaredInConstructor(symbol)) { + type = getTypeOfPropertyDeclaredInConstructor(symbol); + } + else { + let jsdocType: Type | undefined; + let types: Type[] | undefined; + for (const declaration of symbol.declarations) { + const expression = (isBinaryExpression(declaration) || isCallExpression(declaration)) ? declaration : + isAccessExpression(declaration) ? isBinaryExpression(declaration.parent) ? declaration.parent : declaration : + undefined; + if (!expression) { + continue; // Non-assignment declaration merged in (eg, an Identifier to mark the thing as a namespace) - skip over it and pull type info from elsewhere + } - const kind = isAccessExpression(expression) - ? getAssignmentDeclarationPropertyAccessKind(expression) - : getAssignmentDeclarationKind(expression); - if (kind === AssignmentDeclarationKind.ThisProperty) { - if (isDeclarationInConstructor(expression)) { - definedInConstructor = true; + const kind = isAccessExpression(expression) + ? getAssignmentDeclarationPropertyAccessKind(expression) + : getAssignmentDeclarationKind(expression); + if (kind === AssignmentDeclarationKind.ThisProperty) { + if (isDeclarationInConstructor(expression)) { + definedInConstructor = true; + } + else { + definedInMethod = true; + } } - else { - definedInMethod = true; + if (!isCallExpression(expression)) { + jsdocType = getAnnotatedTypeForAssignmentDeclaration(jsdocType, expression, symbol, declaration); + } + if (!jsdocType) { + (types || (types = [])).push((isBinaryExpression(expression) || isCallExpression(expression)) ? getInitializerTypeFromAssignmentDeclaration(symbol, resolvedSymbol, expression, kind) : neverType); } } - if (!isCallExpression(expression)) { - jsdocType = getAnnotatedTypeForAssignmentDeclaration(jsdocType, expression, symbol, declaration); - } - if (!jsdocType) { - (types || (types = [])).push((isBinaryExpression(expression) || isCallExpression(expression)) ? getInitializerTypeFromAssignmentDeclaration(symbol, resolvedSymbol, expression, kind) : neverType); - } - } - let type = jsdocType; - if (!type) { - if (!length(types)) { - return errorType; // No types from any declarations :( - } - let constructorTypes = definedInConstructor ? getConstructorDefinedThisAssignmentTypes(types!, symbol.declarations) : undefined; - // use only the constructor types unless they were only assigned null | undefined (including widening variants) - if (definedInMethod) { - const propType = getTypeOfAssignmentDeclarationPropertyOfBaseType(symbol); - if (propType) { - (constructorTypes || (constructorTypes = [])).push(propType); - definedInConstructor = true; + type = jsdocType; + if (!type) { + if (!length(types)) { + return errorType; // No types from any declarations :( + } + let constructorTypes = definedInConstructor ? getConstructorDefinedThisAssignmentTypes(types!, symbol.declarations) : undefined; + // use only the constructor types unless they were only assigned null | undefined (including widening variants) + if (definedInMethod) { + const propType = getTypeOfAssignmentDeclarationPropertyOfBaseType(symbol); + if (propType) { + (constructorTypes || (constructorTypes = [])).push(propType); + definedInConstructor = true; + } } + const sourceTypes = some(constructorTypes, t => !!(t.flags & ~TypeFlags.Nullable)) ? constructorTypes : types; // TODO: GH#18217 + type = getUnionType(sourceTypes!, UnionReduction.Subtype); } - const sourceTypes = some(constructorTypes, t => !!(t.flags & ~TypeFlags.Nullable)) ? constructorTypes : types; // TODO: GH#18217 - type = getUnionType(sourceTypes!, UnionReduction.Subtype); } const widened = getWidenedType(addOptionality(type, definedInMethod && !definedInConstructor)); if (filterType(widened, t => !!(t.flags & ~TypeFlags.Nullable)) === neverType) { @@ -12704,6 +12743,9 @@ namespace ts { if (accessFlags & AccessFlags.CacheSymbol) { getNodeLinks(accessNode!).resolvedSymbol = prop; } + if (isThisPropertyAccessInConstructor(accessExpression, prop)) { + return autoType; + } } const propType = getTypeOfSymbol(prop); return accessExpression && getAssignmentTargetKind(accessExpression) !== AssignmentKind.Definite ? @@ -24031,6 +24073,10 @@ namespace ts { return false; } + function isThisPropertyAccessInConstructor(node: ElementAccessExpression | PropertyAccessExpression | QualifiedName, prop: Symbol) { + return isThisProperty(node) && isPropertyDeclaredInConstructor(prop) && getThisContainer(node, /*includeArrowFunctions*/ true) === getDeclaringConstructor(prop); + } + function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, leftType: Type, right: Identifier | PrivateIdentifier) { const parentSymbol = getNodeLinks(left).resolvedSymbol; const assignmentKind = getAssignmentTargetKind(node); @@ -24105,7 +24151,7 @@ namespace ts { error(right, Diagnostics.Cannot_assign_to_0_because_it_is_a_read_only_property, idText(right)); return errorType; } - propType = getConstraintForLocation(getTypeOfSymbol(prop), node); + propType = isThisPropertyAccessInConstructor(node, prop) ? autoType : getConstraintForLocation(getTypeOfSymbol(prop), node); } return getFlowTypeOfAccessExpression(node, prop, propType, right); } @@ -24120,6 +24166,9 @@ namespace ts { prop && !(prop.flags & (SymbolFlags.Variable | SymbolFlags.Property | SymbolFlags.Accessor)) && !(prop.flags & SymbolFlags.Method && propType.flags & TypeFlags.Union)) { return propType; } + if (propType === autoType) { + return getFlowTypeOfReference(node, autoType, undefinedType); + } // If strict null checks and strict property initialization checks are enabled, if we have // a this.xxx property access, if the property is an instance property without an initializer, // and if we are in a constructor of the same class as the property declaration, assume that diff --git a/src/compiler/types.ts b/src/compiler/types.ts index b0a0658130b7b..419a9b4b6f58c 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4156,6 +4156,7 @@ namespace ts { deferralParent?: Type; // Source union/intersection of a deferred type cjsExportMerged?: Symbol; // Version of the symbol with all non export= exports merged with the export= target typeOnlyDeclaration?: TypeOnlyCompatibleAliasDeclaration | false; // First resolved alias declaration that makes the symbol only usable in type constructs + isPropertyDeclaredInConstructor?: boolean; // Property declared through 'this.x = ...' assignment in constructor } /* @internal */ From 957d9e64181745de2a70fc87467c675c69765ae1 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 12 Apr 2020 15:28:25 -0700 Subject: [PATCH 02/10] Accept new baselines --- ...onstraintOfJavascriptClassExpression.types | 4 ++-- .../checkJsFiles_noErrorLocation.types | 4 ++-- .../checkSuperCallBeforeThisAccessing9.types | 4 ++-- .../classCanExtendConstructorFunction.types | 4 ++-- .../reference/classExtendingAny.types | 4 ++-- .../reference/exportNestedNamespaces.types | 4 ++-- ...inferringClassMembersFromAssignments.types | 12 +++++------ ...nferringClassMembersFromAssignments3.types | 4 ++-- ...nferringClassMembersFromAssignments5.types | 4 ++-- .../reference/jsDeclarationsClasses.types | 20 +++++++++---------- ...rationsExportAssignedClassExpression.types | 4 ++-- ...portAssignedClassExpressionAnonymous.types | 4 ++-- ...ignedClassExpressionAnonymousWithSub.types | 8 ++++---- ...portAssignedClassExpressionShadowing.types | 4 ++-- ...DeclarationsExportAssignedVisibility.types | 8 ++++---- .../jsDeclarationsTypedefAndImportTypes.types | 4 ++-- .../reference/jsFileClassPropertyType.types | 4 ++-- .../reference/jsFileClassPropertyType3.types | 8 ++++---- .../jsFileClassSelfReferencedProperty.types | 10 +++++----- .../jsObjectsMarkedAsOpenEnded.types | 4 ++-- .../reference/jsdocAccessibilityTags.types | 12 +++++------ .../reference/jsdocAugmentsMissingType.types | 4 ++-- .../reference/jsdocAugments_noExtends.types | 4 ++-- .../reference/jsdocFunctionType.types | 4 ++-- .../jsdocImplements_missingType.types | 4 ++-- .../jsdocImplements_properties.types | 8 ++++---- .../baselines/reference/jsdocImportType.types | 4 ++-- .../reference/jsdocImportType2.types | 4 ++-- tests/baselines/reference/jsdocReadonly.types | 4 ++-- .../reference/jsdocTemplateClass.types | 4 ++-- ...peReferenceToImportOfClassExpression.types | 4 ++-- ...eferenceToImportOfFunctionExpression.types | 4 ++-- .../reference/jsdocTypeTagCast.types | 12 +++++------ .../reference/moduleExportAssignment6.types | 8 ++++---- .../moduleExportNestedNamespaces.types | 4 ++-- .../reference/returnTagTypeGuard.types | 8 ++++---- .../thisPropertyAssignmentCircular.types | 4 ++-- .../thisPropertyOverridesAccessors.types | 4 ++-- .../typeFromParamTagForFunction.types | 12 +++++------ .../typeFromPropertyAssignment15.types | 4 ++-- .../typeFromPropertyAssignment2.types | 4 ++-- .../typeFromPropertyAssignment23.types | 4 ++-- .../typeFromPropertyAssignment25.types | 8 ++++---- .../typeFromPropertyAssignment26.types | 4 ++-- .../typeFromPropertyAssignment28.types | 4 ++-- .../typeFromPropertyAssignment3.types | 4 ++-- .../typeFromPropertyAssignment35.types | 4 ++-- .../reference/varRequireFromJavascript.types | 4 ++-- 48 files changed, 137 insertions(+), 137 deletions(-) diff --git a/tests/baselines/reference/checkIndexConstraintOfJavascriptClassExpression.types b/tests/baselines/reference/checkIndexConstraintOfJavascriptClassExpression.types index 5d58047f6e40b..465e3faea627f 100644 --- a/tests/baselines/reference/checkIndexConstraintOfJavascriptClassExpression.types +++ b/tests/baselines/reference/checkIndexConstraintOfJavascriptClassExpression.types @@ -23,9 +23,9 @@ someFunction(function(BaseClass) { this.foo = "bar"; >this.foo = "bar" : "bar" ->this.foo : string +>this.foo : any >this : this ->foo : string +>foo : any >"bar" : "bar" } _render(error) { diff --git a/tests/baselines/reference/checkJsFiles_noErrorLocation.types b/tests/baselines/reference/checkJsFiles_noErrorLocation.types index d714782fed1c5..b0dceb2d33cad 100644 --- a/tests/baselines/reference/checkJsFiles_noErrorLocation.types +++ b/tests/baselines/reference/checkJsFiles_noErrorLocation.types @@ -25,9 +25,9 @@ class B extends A { this.foo = () => 3; >this.foo = () => 3 : () => number ->this.foo : () => number +>this.foo : any >this : this ->foo : () => number +>foo : any >() => 3 : () => number >3 : 3 } diff --git a/tests/baselines/reference/checkSuperCallBeforeThisAccessing9.types b/tests/baselines/reference/checkSuperCallBeforeThisAccessing9.types index 577256400b2d7..c39389422f0bb 100644 --- a/tests/baselines/reference/checkSuperCallBeforeThisAccessing9.types +++ b/tests/baselines/reference/checkSuperCallBeforeThisAccessing9.types @@ -12,9 +12,9 @@ class Derived { this.x = 10; >this.x = 10 : 10 ->this.x : number +>this.x : any >this : this ->x : number +>x : any >10 : 10 var that = this; diff --git a/tests/baselines/reference/classCanExtendConstructorFunction.types b/tests/baselines/reference/classCanExtendConstructorFunction.types index c7a6770563aaa..268f98d4ca1e6 100644 --- a/tests/baselines/reference/classCanExtendConstructorFunction.types +++ b/tests/baselines/reference/classCanExtendConstructorFunction.types @@ -91,9 +91,9 @@ class Sql extends Wagon { this.foonly = 12 >this.foonly = 12 : 12 ->this.foonly : number +>this.foonly : any >this : this ->foonly : number +>foonly : any >12 : 12 } /** diff --git a/tests/baselines/reference/classExtendingAny.types b/tests/baselines/reference/classExtendingAny.types index fb585ed1de0d1..0916674963d44 100644 --- a/tests/baselines/reference/classExtendingAny.types +++ b/tests/baselines/reference/classExtendingAny.types @@ -67,9 +67,9 @@ class B extends Err { this.wat = 12 >this.wat = 12 : 12 ->this.wat : number +>this.wat : any >this : this ->wat : number +>wat : any >12 : 12 } f() { diff --git a/tests/baselines/reference/exportNestedNamespaces.types b/tests/baselines/reference/exportNestedNamespaces.types index 97ce99af19a02..4bc7568885e18 100644 --- a/tests/baselines/reference/exportNestedNamespaces.types +++ b/tests/baselines/reference/exportNestedNamespaces.types @@ -32,9 +32,9 @@ exports.Classic = class { constructor() { this.p = 1 >this.p = 1 : 1 ->this.p : number +>this.p : any >this : this ->p : number +>p : any >1 : 1 } } diff --git a/tests/baselines/reference/inferringClassMembersFromAssignments.types b/tests/baselines/reference/inferringClassMembersFromAssignments.types index 3d168707fe62a..c962aa841d821 100644 --- a/tests/baselines/reference/inferringClassMembersFromAssignments.types +++ b/tests/baselines/reference/inferringClassMembersFromAssignments.types @@ -11,24 +11,24 @@ class C { this.inConstructor = 0; >this.inConstructor = 0 : 0 ->this.inConstructor : string | number +>this.inConstructor : any >this : this ->inConstructor : string | number +>inConstructor : any >0 : 0 } else { this.inConstructor = "string" >this.inConstructor = "string" : "string" ->this.inConstructor : string | number +>this.inConstructor : any >this : this ->inConstructor : string | number +>inConstructor : any >"string" : "string" } this.inMultiple = 0; >this.inMultiple = 0 : 0 ->this.inMultiple : number +>this.inMultiple : any >this : this ->inMultiple : number +>inMultiple : any >0 : 0 } method() { diff --git a/tests/baselines/reference/inferringClassMembersFromAssignments3.types b/tests/baselines/reference/inferringClassMembersFromAssignments3.types index 60d545b2832fe..66394eb9ed143 100644 --- a/tests/baselines/reference/inferringClassMembersFromAssignments3.types +++ b/tests/baselines/reference/inferringClassMembersFromAssignments3.types @@ -5,9 +5,9 @@ class Base { constructor() { this.p = 1 >this.p = 1 : 1 ->this.p : number +>this.p : any >this : this ->p : number +>p : any >1 : 1 } } diff --git a/tests/baselines/reference/inferringClassMembersFromAssignments5.types b/tests/baselines/reference/inferringClassMembersFromAssignments5.types index fc95b82e5fdeb..6bc1f2482d857 100644 --- a/tests/baselines/reference/inferringClassMembersFromAssignments5.types +++ b/tests/baselines/reference/inferringClassMembersFromAssignments5.types @@ -25,9 +25,9 @@ class Derived extends Base { // should be OK, and p should have type number from this assignment this.p = 1 >this.p = 1 : 1 ->this.p : number +>this.p : any >this : this ->p : number +>p : any >1 : 1 } test() { diff --git a/tests/baselines/reference/jsDeclarationsClasses.types b/tests/baselines/reference/jsDeclarationsClasses.types index bbcea688c2691..853bb6b89969f 100644 --- a/tests/baselines/reference/jsDeclarationsClasses.types +++ b/tests/baselines/reference/jsDeclarationsClasses.types @@ -209,16 +209,16 @@ export class K { constructor() { this.p1 = 12; >this.p1 = 12 : 12 ->this.p1 : number +>this.p1 : any >this : this ->p1 : number +>p1 : any >12 : 12 this.p2 = "ok"; >this.p2 = "ok" : "ok" ->this.p2 : string +>this.p2 : any >this : this ->p2 : string +>p2 : any >"ok" : "ok" } @@ -243,9 +243,9 @@ export class M extends null { constructor() { this.prop = 12; >this.prop = 12 : 12 ->this.prop : number +>this.prop : any >this : this ->prop : number +>prop : any >12 : 12 } } @@ -270,9 +270,9 @@ export class N extends L { this.another = param; >this.another = param : T ->this.another : T +>this.another : any >this : this ->another : T +>another : any >param : T } } @@ -298,9 +298,9 @@ export class O extends N { this.another2 = param; >this.another2 = param : U ->this.another2 : U +>this.another2 : any >this : this ->another2 : U +>another2 : any >param : U } } diff --git a/tests/baselines/reference/jsDeclarationsExportAssignedClassExpression.types b/tests/baselines/reference/jsDeclarationsExportAssignedClassExpression.types index b2c7f801eb747..301d4f4adc528 100644 --- a/tests/baselines/reference/jsDeclarationsExportAssignedClassExpression.types +++ b/tests/baselines/reference/jsDeclarationsExportAssignedClassExpression.types @@ -15,9 +15,9 @@ module.exports = class Thing { this.t = 12 + p; >this.t = 12 + p : number ->this.t : number +>this.t : any >this : this ->t : number +>t : any >12 + p : number >12 : 12 >p : number diff --git a/tests/baselines/reference/jsDeclarationsExportAssignedClassExpressionAnonymous.types b/tests/baselines/reference/jsDeclarationsExportAssignedClassExpressionAnonymous.types index ce144f50ebc53..dc75cbed876fd 100644 --- a/tests/baselines/reference/jsDeclarationsExportAssignedClassExpressionAnonymous.types +++ b/tests/baselines/reference/jsDeclarationsExportAssignedClassExpressionAnonymous.types @@ -14,9 +14,9 @@ module.exports = class { this.t = 12 + p; >this.t = 12 + p : number ->this.t : number +>this.t : any >this : this ->t : number +>t : any >12 + p : number >12 : 12 >p : number diff --git a/tests/baselines/reference/jsDeclarationsExportAssignedClassExpressionAnonymousWithSub.types b/tests/baselines/reference/jsDeclarationsExportAssignedClassExpressionAnonymousWithSub.types index ccf33572b9891..904bbe069441a 100644 --- a/tests/baselines/reference/jsDeclarationsExportAssignedClassExpressionAnonymousWithSub.types +++ b/tests/baselines/reference/jsDeclarationsExportAssignedClassExpressionAnonymousWithSub.types @@ -14,9 +14,9 @@ module.exports = class { this.t = 12 + p; >this.t = 12 + p : number ->this.t : number +>this.t : any >this : this ->t : number +>t : any >12 + p : number >12 : 12 >p : number @@ -34,9 +34,9 @@ module.exports.Sub = class { constructor() { this.instance = new module.exports(10); >this.instance = new module.exports(10) : import("tests/cases/conformance/jsdoc/declarations/index") ->this.instance : import("tests/cases/conformance/jsdoc/declarations/index") +>this.instance : any >this : this ->instance : import("tests/cases/conformance/jsdoc/declarations/index") +>instance : any >new module.exports(10) : import("tests/cases/conformance/jsdoc/declarations/index") >module.exports : typeof import("tests/cases/conformance/jsdoc/declarations/index") >module : { "\"tests/cases/conformance/jsdoc/declarations/index\"": typeof import("tests/cases/conformance/jsdoc/declarations/index"); } diff --git a/tests/baselines/reference/jsDeclarationsExportAssignedClassExpressionShadowing.types b/tests/baselines/reference/jsDeclarationsExportAssignedClassExpressionShadowing.types index b7c41fe7fbb6a..36fdb5bfdc4e3 100644 --- a/tests/baselines/reference/jsDeclarationsExportAssignedClassExpressionShadowing.types +++ b/tests/baselines/reference/jsDeclarationsExportAssignedClassExpressionShadowing.types @@ -25,9 +25,9 @@ module.exports = class Q { constructor() { this.x = new A(); >this.x = new A() : A ->this.x : A +>this.x : any >this : this ->x : A +>x : any >new A() : A >A : typeof A } diff --git a/tests/baselines/reference/jsDeclarationsExportAssignedVisibility.types b/tests/baselines/reference/jsDeclarationsExportAssignedVisibility.types index e54216161b930..a76835e3105a6 100644 --- a/tests/baselines/reference/jsDeclarationsExportAssignedVisibility.types +++ b/tests/baselines/reference/jsDeclarationsExportAssignedVisibility.types @@ -11,9 +11,9 @@ class Container { constructor() { this.usage = new Obj(); >this.usage = new Obj() : import("tests/cases/conformance/jsdoc/declarations/obj") ->this.usage : import("tests/cases/conformance/jsdoc/declarations/obj") +>this.usage : any >this : this ->usage : import("tests/cases/conformance/jsdoc/declarations/obj") +>usage : any >new Obj() : import("tests/cases/conformance/jsdoc/declarations/obj") >Obj : typeof import("tests/cases/conformance/jsdoc/declarations/obj") } @@ -38,9 +38,9 @@ module.exports = class Obj { constructor() { this.x = 12; >this.x = 12 : 12 ->this.x : number +>this.x : any >this : this ->x : number +>x : any >12 : 12 } } diff --git a/tests/baselines/reference/jsDeclarationsTypedefAndImportTypes.types b/tests/baselines/reference/jsDeclarationsTypedefAndImportTypes.types index 6441a279a4342..d4242cb21a78d 100644 --- a/tests/baselines/reference/jsDeclarationsTypedefAndImportTypes.types +++ b/tests/baselines/reference/jsDeclarationsTypedefAndImportTypes.types @@ -38,9 +38,9 @@ class Wrap { this.connItem = c.item; >this.connItem = c.item : number ->this.connItem : number +>this.connItem : any >this : this ->connItem : number +>connItem : any >c.item : number >c : import("tests/cases/conformance/jsdoc/declarations/conn") >item : number diff --git a/tests/baselines/reference/jsFileClassPropertyType.types b/tests/baselines/reference/jsFileClassPropertyType.types index bf1605d5bc6ef..686560e360522 100644 --- a/tests/baselines/reference/jsFileClassPropertyType.types +++ b/tests/baselines/reference/jsFileClassPropertyType.types @@ -5,9 +5,9 @@ class C { constructor () { this.p = 0; >this.p = 0 : 0 ->this.p : number +>this.p : any >this : this ->p : number +>p : any >0 : 0 } } diff --git a/tests/baselines/reference/jsFileClassPropertyType3.types b/tests/baselines/reference/jsFileClassPropertyType3.types index eea855579aee1..aee74b9787efb 100644 --- a/tests/baselines/reference/jsFileClassPropertyType3.types +++ b/tests/baselines/reference/jsFileClassPropertyType3.types @@ -8,17 +8,17 @@ class C { this.p = null; >this.p = null : null ->this.p : number +>this.p : any >this : this ->p : number +>p : any >null : null } else { this.p = 0; >this.p = 0 : 0 ->this.p : number +>this.p : any >this : this ->p : number +>p : any >0 : 0 } } diff --git a/tests/baselines/reference/jsFileClassSelfReferencedProperty.types b/tests/baselines/reference/jsFileClassSelfReferencedProperty.types index 204eee67c9707..9700c12ddd0da 100644 --- a/tests/baselines/reference/jsFileClassSelfReferencedProperty.types +++ b/tests/baselines/reference/jsFileClassSelfReferencedProperty.types @@ -4,15 +4,15 @@ export class StackOverflowTest { constructor () { this.testStackOverflow = this.testStackOverflow.bind(this) ->this.testStackOverflow = this.testStackOverflow.bind(this) : any +>this.testStackOverflow = this.testStackOverflow.bind(this) : error >this.testStackOverflow : any >this : this >testStackOverflow : any ->this.testStackOverflow.bind(this) : any ->this.testStackOverflow.bind : any ->this.testStackOverflow : any +>this.testStackOverflow.bind(this) : error +>this.testStackOverflow.bind : error +>this.testStackOverflow : undefined >this : this ->testStackOverflow : any +>testStackOverflow : undefined >bind : any >this : this } diff --git a/tests/baselines/reference/jsObjectsMarkedAsOpenEnded.types b/tests/baselines/reference/jsObjectsMarkedAsOpenEnded.types index 996165537bc9b..777f779e9c78c 100644 --- a/tests/baselines/reference/jsObjectsMarkedAsOpenEnded.types +++ b/tests/baselines/reference/jsObjectsMarkedAsOpenEnded.types @@ -20,9 +20,9 @@ class C { constructor() { this.member = {}; >this.member = {} : {} ->this.member : {} +>this.member : any >this : this ->member : {} +>member : any >{} : {} this.member.a = 0; diff --git a/tests/baselines/reference/jsdocAccessibilityTags.types b/tests/baselines/reference/jsdocAccessibilityTags.types index 18a364c4da843..a16c3456f2e67 100644 --- a/tests/baselines/reference/jsdocAccessibilityTags.types +++ b/tests/baselines/reference/jsdocAccessibilityTags.types @@ -52,9 +52,9 @@ class C { */ this.priv2 = 1; >this.priv2 = 1 : 1 ->this.priv2 : number +>this.priv2 : any >this : this ->priv2 : number +>priv2 : any >1 : 1 /** @@ -64,9 +64,9 @@ class C { */ this.prot2 = 2; >this.prot2 = 2 : 2 ->this.prot2 : number +>this.prot2 : any >this : this ->prot2 : number +>prot2 : any >2 : 2 /** @@ -76,9 +76,9 @@ class C { */ this.pub2 = 3; >this.pub2 = 3 : 3 ->this.pub2 : number +>this.pub2 : any >this : this ->pub2 : number +>pub2 : any >3 : 3 } h() { return this.priv2 } diff --git a/tests/baselines/reference/jsdocAugmentsMissingType.types b/tests/baselines/reference/jsdocAugmentsMissingType.types index de0ba42cdd26a..430fae254806f 100644 --- a/tests/baselines/reference/jsdocAugmentsMissingType.types +++ b/tests/baselines/reference/jsdocAugmentsMissingType.types @@ -2,9 +2,9 @@ class A { constructor() { this.x = 0; } } >A : A >this.x = 0 : 0 ->this.x : number +>this.x : any >this : this ->x : number +>x : any >0 : 0 /** @augments */ diff --git a/tests/baselines/reference/jsdocAugments_noExtends.types b/tests/baselines/reference/jsdocAugments_noExtends.types index fab9c7b7c84e8..31c79acc60a5a 100644 --- a/tests/baselines/reference/jsdocAugments_noExtends.types +++ b/tests/baselines/reference/jsdocAugments_noExtends.types @@ -2,9 +2,9 @@ class A { constructor() { this.x = 0; } } >A : A >this.x = 0 : 0 ->this.x : number +>this.x : any >this : this ->x : number +>x : any >0 : 0 /** @augments A */ diff --git a/tests/baselines/reference/jsdocFunctionType.types b/tests/baselines/reference/jsdocFunctionType.types index 6349dccb10a49..231c17c7a5082 100644 --- a/tests/baselines/reference/jsdocFunctionType.types +++ b/tests/baselines/reference/jsdocFunctionType.types @@ -44,9 +44,9 @@ class C { this.length = n; >this.length = n : number ->this.length : number +>this.length : any >this : this ->length : number +>length : any >n : number } } diff --git a/tests/baselines/reference/jsdocImplements_missingType.types b/tests/baselines/reference/jsdocImplements_missingType.types index 7a63d6aa5f211..370d360db0a75 100644 --- a/tests/baselines/reference/jsdocImplements_missingType.types +++ b/tests/baselines/reference/jsdocImplements_missingType.types @@ -2,9 +2,9 @@ class A { constructor() { this.x = 0; } } >A : A >this.x = 0 : 0 ->this.x : number +>this.x : any >this : this ->x : number +>x : any >0 : 0 /** @implements */ diff --git a/tests/baselines/reference/jsdocImplements_properties.types b/tests/baselines/reference/jsdocImplements_properties.types index fb00977b860e2..79f7a3d5a0bc6 100644 --- a/tests/baselines/reference/jsdocImplements_properties.types +++ b/tests/baselines/reference/jsdocImplements_properties.types @@ -2,9 +2,9 @@ class A { constructor() { this.x = 0; } } >A : A >this.x = 0 : 0 ->this.x : number +>this.x : any >this : this ->x : number +>x : any >0 : 0 /** @implements A*/ @@ -26,9 +26,9 @@ class B3 { constructor() { this.x = 10 } >this.x = 10 : 10 ->this.x : number +>this.x : any >this : this ->x : number +>x : any >10 : 10 } diff --git a/tests/baselines/reference/jsdocImportType.types b/tests/baselines/reference/jsdocImportType.types index fd3157c19dc1a..b1d7542475079 100644 --- a/tests/baselines/reference/jsdocImportType.types +++ b/tests/baselines/reference/jsdocImportType.types @@ -45,9 +45,9 @@ class Chunk { constructor() { this.chunk = 1; >this.chunk = 1 : 1 ->this.chunk : number +>this.chunk : any >this : this ->chunk : number +>chunk : any >1 : 1 } } diff --git a/tests/baselines/reference/jsdocImportType2.types b/tests/baselines/reference/jsdocImportType2.types index 635dfc1ff1e76..f4386faf5a695 100644 --- a/tests/baselines/reference/jsdocImportType2.types +++ b/tests/baselines/reference/jsdocImportType2.types @@ -50,9 +50,9 @@ module.exports = class Chunk { constructor() { this.chunk = 1; >this.chunk = 1 : 1 ->this.chunk : number +>this.chunk : any >this : this ->chunk : number +>chunk : any >1 : 1 } } diff --git a/tests/baselines/reference/jsdocReadonly.types b/tests/baselines/reference/jsdocReadonly.types index 8a0f0806a2497..806623749cd3d 100644 --- a/tests/baselines/reference/jsdocReadonly.types +++ b/tests/baselines/reference/jsdocReadonly.types @@ -35,9 +35,9 @@ class LOL { /** @readonly ok */ this.ka = 2 >this.ka = 2 : 2 ->this.ka : number +>this.ka : any >this : this ->ka : number +>ka : any >2 : 2 } } diff --git a/tests/baselines/reference/jsdocTemplateClass.types b/tests/baselines/reference/jsdocTemplateClass.types index 6e5695ca83787..0479abccf3ebd 100644 --- a/tests/baselines/reference/jsdocTemplateClass.types +++ b/tests/baselines/reference/jsdocTemplateClass.types @@ -14,9 +14,9 @@ class Foo { this.a = x >this.a = x : T ->this.a : T +>this.a : any >this : this ->a : T +>a : any >x : T } /** diff --git a/tests/baselines/reference/jsdocTypeReferenceToImportOfClassExpression.types b/tests/baselines/reference/jsdocTypeReferenceToImportOfClassExpression.types index 98f18bc8fb086..0645f6adfda1f 100644 --- a/tests/baselines/reference/jsdocTypeReferenceToImportOfClassExpression.types +++ b/tests/baselines/reference/jsdocTypeReferenceToImportOfClassExpression.types @@ -39,9 +39,9 @@ class MW { this.compiler = compiler; >this.compiler = compiler : import("tests/cases/conformance/jsdoc/MC") ->this.compiler : import("tests/cases/conformance/jsdoc/MC") +>this.compiler : any >this : this ->compiler : import("tests/cases/conformance/jsdoc/MC") +>compiler : any >compiler : import("tests/cases/conformance/jsdoc/MC") } } diff --git a/tests/baselines/reference/jsdocTypeReferenceToImportOfFunctionExpression.types b/tests/baselines/reference/jsdocTypeReferenceToImportOfFunctionExpression.types index 1747fecbf6c38..81422f0c76c9d 100644 --- a/tests/baselines/reference/jsdocTypeReferenceToImportOfFunctionExpression.types +++ b/tests/baselines/reference/jsdocTypeReferenceToImportOfFunctionExpression.types @@ -42,9 +42,9 @@ class MW { this.compiler = compiler; >this.compiler = compiler : typeof MC ->this.compiler : typeof MC +>this.compiler : any >this : this ->compiler : typeof MC +>compiler : any >compiler : typeof MC } } diff --git a/tests/baselines/reference/jsdocTypeTagCast.types b/tests/baselines/reference/jsdocTypeTagCast.types index dc40ea0d950d2..f7e9136ca34f5 100644 --- a/tests/baselines/reference/jsdocTypeTagCast.types +++ b/tests/baselines/reference/jsdocTypeTagCast.types @@ -43,9 +43,9 @@ class SomeBase { constructor() { this.p = 42; >this.p = 42 : 42 ->this.p : number +>this.p : any >this : this ->p : number +>p : any >42 : 42 } } @@ -60,9 +60,9 @@ class SomeDerived extends SomeBase { this.x = 42; >this.x = 42 : 42 ->this.x : number +>this.x : any >this : this ->x : number +>x : any >42 : 42 } } @@ -72,9 +72,9 @@ class SomeOther { constructor() { this.q = 42; >this.q = 42 : 42 ->this.q : number +>this.q : any >this : this ->q : number +>q : any >42 : 42 } } diff --git a/tests/baselines/reference/moduleExportAssignment6.types b/tests/baselines/reference/moduleExportAssignment6.types index e1fd4a4dd1253..ee4122873c0c8 100644 --- a/tests/baselines/reference/moduleExportAssignment6.types +++ b/tests/baselines/reference/moduleExportAssignment6.types @@ -8,16 +8,16 @@ class C { this.x = x >this.x = x : number ->this.x : number +>this.x : any >this : this ->x : number +>x : any >x : number this.exports = [x] >this.exports = [x] : number[] ->this.exports : number[] +>this.exports : any >this : this ->exports : number[] +>exports : any >[x] : number[] >x : number } diff --git a/tests/baselines/reference/moduleExportNestedNamespaces.types b/tests/baselines/reference/moduleExportNestedNamespaces.types index 249739a3beb8a..a1fc39b723c68 100644 --- a/tests/baselines/reference/moduleExportNestedNamespaces.types +++ b/tests/baselines/reference/moduleExportNestedNamespaces.types @@ -39,9 +39,9 @@ module.exports.Classic = class { constructor() { this.p = 1 >this.p = 1 : 1 ->this.p : number +>this.p : any >this : this ->p : number +>p : any >1 : 1 } } diff --git a/tests/baselines/reference/returnTagTypeGuard.types b/tests/baselines/reference/returnTagTypeGuard.types index a8211118ea945..7056faa133697 100644 --- a/tests/baselines/reference/returnTagTypeGuard.types +++ b/tests/baselines/reference/returnTagTypeGuard.types @@ -5,9 +5,9 @@ class Entry { constructor() { this.c = 1 >this.c = 1 : 1 ->this.c : number +>this.c : any >this : this ->c : number +>c : any >1 : 1 } /** @@ -28,9 +28,9 @@ class Group { constructor() { this.d = 'no' >this.d = 'no' : "no" ->this.d : string +>this.d : any >this : this ->d : string +>d : any >'no' : "no" } /** diff --git a/tests/baselines/reference/thisPropertyAssignmentCircular.types b/tests/baselines/reference/thisPropertyAssignmentCircular.types index 1b5f8ae3dea32..2c1dc0cdb70e5 100644 --- a/tests/baselines/reference/thisPropertyAssignmentCircular.types +++ b/tests/baselines/reference/thisPropertyAssignmentCircular.types @@ -5,9 +5,9 @@ export class Foo { constructor() { this.foo = "Hello"; >this.foo = "Hello" : "Hello" ->this.foo : string +>this.foo : any >this : this ->foo : string +>foo : any >"Hello" : "Hello" } slicey() { diff --git a/tests/baselines/reference/thisPropertyOverridesAccessors.types b/tests/baselines/reference/thisPropertyOverridesAccessors.types index c242351dbd2b2..84da977ac1250 100644 --- a/tests/baselines/reference/thisPropertyOverridesAccessors.types +++ b/tests/baselines/reference/thisPropertyOverridesAccessors.types @@ -23,9 +23,9 @@ class Bar extends Foo { this.p = 2 >this.p = 2 : 2 ->this.p : number +>this.p : any >this : this ->p : number +>p : any >2 : 2 } } diff --git a/tests/baselines/reference/typeFromParamTagForFunction.types b/tests/baselines/reference/typeFromParamTagForFunction.types index 476c6c3c4539e..2c454f3e0da4b 100644 --- a/tests/baselines/reference/typeFromParamTagForFunction.types +++ b/tests/baselines/reference/typeFromParamTagForFunction.types @@ -50,9 +50,9 @@ exports.B = class { constructor() { this.x = 1; >this.x = 1 : 1 ->this.x : number +>this.x : any >this : this ->x : number +>x : any >1 : 1 } }; @@ -135,9 +135,9 @@ export class E { constructor() { this.x = 1; >this.x = 1 : 1 ->this.x : number +>this.x : any >this : this ->x : number +>x : any >1 : 1 } } @@ -206,9 +206,9 @@ class H { constructor() { this.x = 1; >this.x = 1 : 1 ->this.x : number +>this.x : any >this : this ->x : number +>x : any >1 : 1 } } diff --git a/tests/baselines/reference/typeFromPropertyAssignment15.types b/tests/baselines/reference/typeFromPropertyAssignment15.types index e1573a21e758b..c82b777396eee 100644 --- a/tests/baselines/reference/typeFromPropertyAssignment15.types +++ b/tests/baselines/reference/typeFromPropertyAssignment15.types @@ -13,9 +13,9 @@ Outer.Inner = class { constructor() { this.x = 1 >this.x = 1 : 1 ->this.x : number +>this.x : any >this : this ->x : number +>x : any >1 : 1 } m() { } diff --git a/tests/baselines/reference/typeFromPropertyAssignment2.types b/tests/baselines/reference/typeFromPropertyAssignment2.types index c7f5619ab67c6..aa06b79f4844b 100644 --- a/tests/baselines/reference/typeFromPropertyAssignment2.types +++ b/tests/baselines/reference/typeFromPropertyAssignment2.types @@ -20,9 +20,9 @@ Outer.Inner = class I { constructor() { this.x = 1 >this.x = 1 : 1 ->this.x : number +>this.x : any >this : this ->x : number +>x : any >1 : 1 } } diff --git a/tests/baselines/reference/typeFromPropertyAssignment23.types b/tests/baselines/reference/typeFromPropertyAssignment23.types index 03bf1d91776c2..d100f71884056 100644 --- a/tests/baselines/reference/typeFromPropertyAssignment23.types +++ b/tests/baselines/reference/typeFromPropertyAssignment23.types @@ -5,9 +5,9 @@ class B { constructor () { this.n = 1 >this.n = 1 : 1 ->this.n : number +>this.n : any >this : this ->n : number +>n : any >1 : 1 } foo() { diff --git a/tests/baselines/reference/typeFromPropertyAssignment25.types b/tests/baselines/reference/typeFromPropertyAssignment25.types index d27b4a70165c2..e5ec6cf6f04fe 100644 --- a/tests/baselines/reference/typeFromPropertyAssignment25.types +++ b/tests/baselines/reference/typeFromPropertyAssignment25.types @@ -13,9 +13,9 @@ Common.I = class { constructor() { this.i = 1 >this.i = 1 : 1 ->this.i : number +>this.i : any >this : this ->i : number +>i : any >1 : 1 } } @@ -36,9 +36,9 @@ Common.O = class extends Common.I { this.o = 2 >this.o = 2 : 2 ->this.o : number +>this.o : any >this : this ->o : number +>o : any >2 : 2 } } diff --git a/tests/baselines/reference/typeFromPropertyAssignment26.types b/tests/baselines/reference/typeFromPropertyAssignment26.types index a6bc478348842..dc01eed98d830 100644 --- a/tests/baselines/reference/typeFromPropertyAssignment26.types +++ b/tests/baselines/reference/typeFromPropertyAssignment26.types @@ -13,9 +13,9 @@ UI.TreeElement = class { constructor() { this.treeOutline = 12 >this.treeOutline = 12 : 12 ->this.treeOutline : number +>this.treeOutline : any >this : this ->treeOutline : number +>treeOutline : any >12 : 12 } }; diff --git a/tests/baselines/reference/typeFromPropertyAssignment28.types b/tests/baselines/reference/typeFromPropertyAssignment28.types index dc25e6426be5c..a84ea102a87cb 100644 --- a/tests/baselines/reference/typeFromPropertyAssignment28.types +++ b/tests/baselines/reference/typeFromPropertyAssignment28.types @@ -3,9 +3,9 @@ class C { constructor() { this.p = 1; } } >C : C >this.p = 1 : 1 ->this.p : number +>this.p : any >this : this ->p : number +>p : any >1 : 1 // Property assignment does nothing. diff --git a/tests/baselines/reference/typeFromPropertyAssignment3.types b/tests/baselines/reference/typeFromPropertyAssignment3.types index 3c3e34b518abd..510296439c894 100644 --- a/tests/baselines/reference/typeFromPropertyAssignment3.types +++ b/tests/baselines/reference/typeFromPropertyAssignment3.types @@ -22,9 +22,9 @@ Outer.Inner = class I { constructor() { this.x = 1 >this.x = 1 : 1 ->this.x : number +>this.x : any >this : this ->x : number +>x : any >1 : 1 } } diff --git a/tests/baselines/reference/typeFromPropertyAssignment35.types b/tests/baselines/reference/typeFromPropertyAssignment35.types index 4eebf48bbc168..78f1dfd6224ad 100644 --- a/tests/baselines/reference/typeFromPropertyAssignment35.types +++ b/tests/baselines/reference/typeFromPropertyAssignment35.types @@ -34,9 +34,9 @@ Emu.D = class { constructor() { this._model = 1 >this._model = 1 : 1 ->this._model : number +>this._model : any >this : this ->_model : number +>_model : any >1 : 1 } } diff --git a/tests/baselines/reference/varRequireFromJavascript.types b/tests/baselines/reference/varRequireFromJavascript.types index 7537eecb6396a..f6ef20d1a1505 100644 --- a/tests/baselines/reference/varRequireFromJavascript.types +++ b/tests/baselines/reference/varRequireFromJavascript.types @@ -44,9 +44,9 @@ export class Crunch { this.n = n >this.n = n : number ->this.n : number +>this.n : any >this : this ->n : number +>n : any >n : number } m() { From 6e0274a98a7451e20803b1fd3fa294ad4540d5d8 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 14 Apr 2020 10:13:51 -0700 Subject: [PATCH 03/10] Also use CFA in constructor functions --- src/compiler/binder.ts | 6 +++--- src/compiler/checker.ts | 39 ++++++++++++++++++++++++++------------- src/compiler/types.ts | 5 +++-- 3 files changed, 32 insertions(+), 18 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 3cde8be7ea115..4a022f3f03ec8 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -659,7 +659,7 @@ namespace ts { } // We create a return control flow graph for IIFEs and constructors. For constructors // we use the return control flow graph in strict property initialization checks. - currentReturnTarget = isIIFE || node.kind === SyntaxKind.Constructor ? createBranchLabel() : undefined; + currentReturnTarget = isIIFE || node.kind === SyntaxKind.Constructor || (isInJSFile && (node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.FunctionExpression)) ? createBranchLabel() : undefined; currentExceptionTarget = undefined; currentBreakTarget = undefined; currentContinueTarget = undefined; @@ -680,8 +680,8 @@ namespace ts { if (currentReturnTarget) { addAntecedent(currentReturnTarget, currentFlow); currentFlow = finishFlowLabel(currentReturnTarget); - if (node.kind === SyntaxKind.Constructor) { - (node).returnFlowNode = currentFlow; + if (node.kind === SyntaxKind.Constructor || (isInJSFile && (node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.FunctionExpression))) { + (node).returnFlowNode = currentFlow; } } if (!isIIFE) { diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f348e59d8d4e6..6729a6c001542 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7500,17 +7500,21 @@ namespace ts { return undefined; } - function isPropertyDeclaredInConstructor(symbol: Symbol) { + function isConstructorDeclaredProperty(symbol: Symbol) { + // A propery is considered a constructor declared property when all declaration sites are this.xxx assignments, + // when no declaration sites have JSDoc type annotations, and when at least one declaration site is in the body of + // a class constructor. if (symbol.valueDeclaration && isBinaryExpression(symbol.valueDeclaration)) { const links = getSymbolLinks(symbol); - if (links.isPropertyDeclaredInConstructor === undefined) { - links.isPropertyDeclaredInConstructor = !!getDeclaringConstructor(symbol) && every(symbol.declarations, declaration => + if (links.isConstructorDeclaredProperty === undefined) { + links.isConstructorDeclaredProperty = !!getDeclaringConstructor(symbol) && every(symbol.declarations, declaration => isBinaryExpression(declaration) && getAssignmentDeclarationKind(declaration) === AssignmentDeclarationKind.ThisProperty && (declaration.left.kind !== SyntaxKind.ElementAccessExpression || isStringOrNumericLiteralLike((declaration.left).argumentExpression)) && !getAnnotatedTypeForAssignmentDeclaration(/*declaredType*/ undefined, declaration, symbol, declaration)); + links.typeOfPropertyInBaseClass = getTypeOfAssignmentDeclarationPropertyOfBaseType(symbol); } - return links.isPropertyDeclaredInConstructor; + return links.isConstructorDeclaredProperty; } return false; } @@ -7518,19 +7522,23 @@ namespace ts { function getDeclaringConstructor(symbol: Symbol) { for (const declaration of symbol.declarations) { const container = getThisContainer(declaration, /*includeArrowFunctions*/ false); - if (container && container.kind === SyntaxKind.Constructor) { + if (container && (container.kind === SyntaxKind.Constructor || isJSConstructor(container))) { return container; } } } - function getTypeOfPropertyDeclaredInConstructor(symbol: Symbol) { + function getTypeOfConstructorDeclaredProperty(symbol: Symbol) { const constructor = getDeclaringConstructor(symbol)!; const reference = createPropertyAccess(createThis(), unescapeLeadingUnderscores(symbol.escapedName)); reference.expression.parent = reference; reference.parent = constructor; reference.flowNode = constructor.returnFlowNode; - return getFlowTypeOfReference(reference, autoType, undefinedType); + const flowType = getFlowTypeOfReference(reference, autoType, getSymbolLinks(symbol).typeOfPropertyInBaseClass || undefinedType); + if (noImplicitAny && (flowType === autoType || flowType === autoArrayType)) { + error(symbol.valueDeclaration, Diagnostics.Member_0_implicitly_has_an_1_type, symbolToString(symbol), typeToString(flowType)); + } + return convertAutoToAny(flowType); } function getWidenedTypeForAssignmentDeclaration(symbol: Symbol, resolvedSymbol?: Symbol) { @@ -7544,13 +7552,18 @@ namespace ts { const containerObjectType = getJSContainerObjectType(symbol.valueDeclaration, symbol, container); return containerObjectType || getWidenedLiteralType(checkExpressionCached(container)); } - let type; + let type = undefined; let definedInConstructor = false; let definedInMethod = false; - if (isPropertyDeclaredInConstructor(symbol)) { - type = getTypeOfPropertyDeclaredInConstructor(symbol); + // We use control flow analysis to determine the type of the property if the property qualifies as a constructor + // declared property and the resulting control flow type isn't just undefined or null. + if (isConstructorDeclaredProperty(symbol)) { + const controlFlowType = getTypeOfConstructorDeclaredProperty(symbol); + if (!everyType(controlFlowType, isNullableType)) { + type = controlFlowType; + } } - else { + if (!type) { let jsdocType: Type | undefined; let types: Type[] | undefined; for (const declaration of symbol.declarations) { @@ -24074,7 +24087,7 @@ namespace ts { } function isThisPropertyAccessInConstructor(node: ElementAccessExpression | PropertyAccessExpression | QualifiedName, prop: Symbol) { - return isThisProperty(node) && isPropertyDeclaredInConstructor(prop) && getThisContainer(node, /*includeArrowFunctions*/ true) === getDeclaringConstructor(prop); + return isThisProperty(node) && isConstructorDeclaredProperty(prop) && getThisContainer(node, /*includeArrowFunctions*/ true) === getDeclaringConstructor(prop); } function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, leftType: Type, right: Identifier | PrivateIdentifier) { @@ -24167,7 +24180,7 @@ namespace ts { return propType; } if (propType === autoType) { - return getFlowTypeOfReference(node, autoType, undefinedType); + return getFlowTypeOfReference(node, autoType, prop && getSymbolLinks(prop).typeOfPropertyInBaseClass || undefinedType); } // If strict null checks and strict property initialization checks are enabled, if we have // a this.xxx property access, if the property is an instance property without an initializer, diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 419a9b4b6f58c..8604f35947d4e 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1107,6 +1107,7 @@ namespace ts { exclamationToken?: ExclamationToken; body?: Block | Expression; /* @internal */ endFlowNode?: FlowNode; + /* @internal */ returnFlowNode?: FlowNode; } export type FunctionLikeDeclaration = @@ -1152,7 +1153,6 @@ namespace ts { kind: SyntaxKind.Constructor; parent: ClassLikeDeclaration; body?: FunctionBody; - /* @internal */ returnFlowNode?: FlowNode; } /** For when we encounter a semicolon in a class declaration. ES6 allows these as class elements. */ @@ -4156,7 +4156,8 @@ namespace ts { deferralParent?: Type; // Source union/intersection of a deferred type cjsExportMerged?: Symbol; // Version of the symbol with all non export= exports merged with the export= target typeOnlyDeclaration?: TypeOnlyCompatibleAliasDeclaration | false; // First resolved alias declaration that makes the symbol only usable in type constructs - isPropertyDeclaredInConstructor?: boolean; // Property declared through 'this.x = ...' assignment in constructor + isConstructorDeclaredProperty?: boolean; // Property declared through 'this.x = ...' assignment in constructor + typeOfPropertyInBaseClass?: Type; // Type of constructor declared property in base class } /* @internal */ From df98d9c7432491a4f7f1545d8536f326d73fc879 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 14 Apr 2020 10:13:58 -0700 Subject: [PATCH 04/10] Accept new baselines --- ...eckExportsObjectAssignPrototypeProperty.types | 4 ++-- .../classCanExtendConstructorFunction.types | 4 ++-- .../reference/constructorFunctions.types | 4 ++-- .../reference/constructorFunctionsStrict.types | 8 ++++---- ...ationsExportAssignedConstructorFunction.types | 4 ++-- .../baselines/reference/jsdocFunctionType.types | 8 ++++---- ...propertiesOfGenericConstructorFunctions.types | 8 ++++---- .../reference/thisPropertyAssignment.types | 6 +++--- .../thisPropertyAssignmentCircular.symbols | 2 ++ .../thisPropertyAssignmentCircular.types | 10 +++++----- .../thisTypeOfConstructorFunctions.types | 12 ++++++------ .../reference/typeFromJSConstructor.errors.txt | 13 ++++++++----- .../reference/typeFromJSConstructor.types | 16 ++++++++-------- .../reference/typeFromPrototypeAssignment.types | 4 ++-- .../reference/typeFromPrototypeAssignment2.types | 4 ++-- 15 files changed, 56 insertions(+), 51 deletions(-) diff --git a/tests/baselines/reference/checkExportsObjectAssignPrototypeProperty.types b/tests/baselines/reference/checkExportsObjectAssignPrototypeProperty.types index a3bb9cd12cefa..6634a1a45ed3d 100644 --- a/tests/baselines/reference/checkExportsObjectAssignPrototypeProperty.types +++ b/tests/baselines/reference/checkExportsObjectAssignPrototypeProperty.types @@ -105,9 +105,9 @@ function Person(name) { this.name = name; >this.name = name : string ->this.name : string +>this.name : any >this : this ->name : string +>name : any >name : string } Person.prototype.describe = function () { diff --git a/tests/baselines/reference/classCanExtendConstructorFunction.types b/tests/baselines/reference/classCanExtendConstructorFunction.types index 268f98d4ca1e6..f667ce4b15097 100644 --- a/tests/baselines/reference/classCanExtendConstructorFunction.types +++ b/tests/baselines/reference/classCanExtendConstructorFunction.types @@ -9,9 +9,9 @@ function Wagon(numberOxen) { this.numberOxen = numberOxen >this.numberOxen = numberOxen : number ->this.numberOxen : number +>this.numberOxen : any >this : this ->numberOxen : number +>numberOxen : any >numberOxen : number } /** @param {Wagon[]=} wagons */ diff --git a/tests/baselines/reference/constructorFunctions.types b/tests/baselines/reference/constructorFunctions.types index ba2fedb66438e..84ad439394dfa 100644 --- a/tests/baselines/reference/constructorFunctions.types +++ b/tests/baselines/reference/constructorFunctions.types @@ -143,9 +143,9 @@ function C6() { this.functions = [x => x, x => x + 1, x => x - 1] >this.functions = [x => x, x => x + 1, x => x - 1] : ((x: any) => any)[] ->this.functions : ((x: any) => any)[] +>this.functions : any >this : this ->functions : ((x: any) => any)[] +>functions : any >[x => x, x => x + 1, x => x - 1] : ((x: any) => any)[] >x => x : (x: any) => any >x : any diff --git a/tests/baselines/reference/constructorFunctionsStrict.types b/tests/baselines/reference/constructorFunctionsStrict.types index daeffbdd7ffc1..a92024920b55d 100644 --- a/tests/baselines/reference/constructorFunctionsStrict.types +++ b/tests/baselines/reference/constructorFunctionsStrict.types @@ -85,10 +85,10 @@ var j = new A(2) k.x === j.x >k.x === j.x : boolean ->k.x : number +>k.x : number | undefined >k : A ->x : number ->j.x : number +>x : number | undefined +>j.x : number | undefined >j : A ->x : number +>x : number | undefined diff --git a/tests/baselines/reference/jsDeclarationsExportAssignedConstructorFunction.types b/tests/baselines/reference/jsDeclarationsExportAssignedConstructorFunction.types index bbdaa717fb273..d7f0ad5272155 100644 --- a/tests/baselines/reference/jsDeclarationsExportAssignedConstructorFunction.types +++ b/tests/baselines/reference/jsDeclarationsExportAssignedConstructorFunction.types @@ -11,9 +11,9 @@ module.exports.MyClass = function() { this.x = 1 >this.x = 1 : 1 ->this.x : number +>this.x : any >this : this ->x : number +>x : any >1 : 1 } module.exports.MyClass.prototype = { diff --git a/tests/baselines/reference/jsdocFunctionType.types b/tests/baselines/reference/jsdocFunctionType.types index 231c17c7a5082..f43ce2e84e2e2 100644 --- a/tests/baselines/reference/jsdocFunctionType.types +++ b/tests/baselines/reference/jsdocFunctionType.types @@ -92,9 +92,9 @@ function D(n) { this.length = n; >this.length = n : number ->this.length : number +>this.length : any >this : this ->length : number +>length : any >n : number } @@ -151,9 +151,9 @@ var E = function(n) { this.not_length_on_purpose = n; >this.not_length_on_purpose = n : number ->this.not_length_on_purpose : number +>this.not_length_on_purpose : any >this : this ->not_length_on_purpose : number +>not_length_on_purpose : any >n : number }; diff --git a/tests/baselines/reference/propertiesOfGenericConstructorFunctions.types b/tests/baselines/reference/propertiesOfGenericConstructorFunctions.types index 71ef32425aba7..42bf2ea2e9459 100644 --- a/tests/baselines/reference/propertiesOfGenericConstructorFunctions.types +++ b/tests/baselines/reference/propertiesOfGenericConstructorFunctions.types @@ -94,16 +94,16 @@ function Cp(t) { this.x = 1 >this.x = 1 : 1 ->this.x : number +>this.x : any >this : this ->x : number +>x : any >1 : 1 this.y = t >this.y = t : T ->this.y : T +>this.y : any >this : this ->y : T +>y : any >t : T } Cp.prototype = { diff --git a/tests/baselines/reference/thisPropertyAssignment.types b/tests/baselines/reference/thisPropertyAssignment.types index c919f47db1eec..ad2459773523e 100644 --- a/tests/baselines/reference/thisPropertyAssignment.types +++ b/tests/baselines/reference/thisPropertyAssignment.types @@ -44,9 +44,9 @@ function F() { this.a = {}; >this.a = {} : {} ->this.a : {} +>this.a : any >this : this ->a : {} +>a : any >{} : {} this.a.b = {}; @@ -60,7 +60,7 @@ function F() { this["b"] = {}; >this["b"] = {} : {} ->this["b"] : {} +>this["b"] : any >this : this >"b" : "b" >{} : {} diff --git a/tests/baselines/reference/thisPropertyAssignmentCircular.symbols b/tests/baselines/reference/thisPropertyAssignmentCircular.symbols index 69bde885d529a..efaf0a1235e09 100644 --- a/tests/baselines/reference/thisPropertyAssignmentCircular.symbols +++ b/tests/baselines/reference/thisPropertyAssignmentCircular.symbols @@ -44,8 +44,10 @@ function C() { >this.x : Symbol(C.x, Decl(thisPropertyAssignmentCircular.js, 13, 14), Decl(thisPropertyAssignmentCircular.js, 14, 15)) >this : Symbol(C, Decl(thisPropertyAssignmentCircular.js, 10, 1)) >x : Symbol(C.x, Decl(thisPropertyAssignmentCircular.js, 13, 14), Decl(thisPropertyAssignmentCircular.js, 14, 15)) +>this.x.toString : Symbol(Function.toString, Decl(lib.es5.d.ts, --, --)) >this.x : Symbol(C.x, Decl(thisPropertyAssignmentCircular.js, 13, 14), Decl(thisPropertyAssignmentCircular.js, 14, 15)) >this : Symbol(C, Decl(thisPropertyAssignmentCircular.js, 10, 1)) >x : Symbol(C.x, Decl(thisPropertyAssignmentCircular.js, 13, 14), Decl(thisPropertyAssignmentCircular.js, 14, 15)) +>toString : Symbol(Function.toString, Decl(lib.es5.d.ts, --, --)) } diff --git a/tests/baselines/reference/thisPropertyAssignmentCircular.types b/tests/baselines/reference/thisPropertyAssignmentCircular.types index 2c1dc0cdb70e5..bdeba6dfebace 100644 --- a/tests/baselines/reference/thisPropertyAssignmentCircular.types +++ b/tests/baselines/reference/thisPropertyAssignmentCircular.types @@ -52,11 +52,11 @@ function C() { >this : this >x : any >function() { this.x.toString(); } : () => void ->this.x.toString() : any ->this.x.toString : any ->this.x : any +>this.x.toString() : string +>this.x.toString : () => string +>this.x : () => void >this : this ->x : any ->toString : any +>x : () => void +>toString : () => string } diff --git a/tests/baselines/reference/thisTypeOfConstructorFunctions.types b/tests/baselines/reference/thisTypeOfConstructorFunctions.types index 126c0c9321a2e..2bd9ada0ac3d2 100644 --- a/tests/baselines/reference/thisTypeOfConstructorFunctions.types +++ b/tests/baselines/reference/thisTypeOfConstructorFunctions.types @@ -18,17 +18,17 @@ function Cp(t) { this.y = t >this.y = t : T ->this.y : T +>this.y : any >this : this ->y : T +>y : any >t : T /** @return {this} */ this.m3 = () => this >this.m3 = () => this : () => this ->this.m3 : () => this +>this.m3 : any >this : this ->m3 : () => this +>m3 : any >() => this : () => this >this : this } @@ -67,9 +67,9 @@ function Cpp(t) { this.y = t >this.y = t : T ->this.y : T +>this.y : any >this : this ->y : T +>y : any >t : T } /** @return {this} */ diff --git a/tests/baselines/reference/typeFromJSConstructor.errors.txt b/tests/baselines/reference/typeFromJSConstructor.errors.txt index ffcf5393f516d..d77ca8ebd165d 100644 --- a/tests/baselines/reference/typeFromJSConstructor.errors.txt +++ b/tests/baselines/reference/typeFromJSConstructor.errors.txt @@ -1,12 +1,13 @@ tests/cases/conformance/salsa/a.js(10,5): error TS7008: Member 'twices' implicitly has an 'any[]' type. tests/cases/conformance/salsa/a.js(14,5): error TS2322: Type '"hi"' is not assignable to type 'number'. +tests/cases/conformance/salsa/a.js(17,5): error TS2322: Type 'undefined' is not assignable to type 'string'. tests/cases/conformance/salsa/a.js(21,5): error TS2322: Type 'false' is not assignable to type 'number'. -tests/cases/conformance/salsa/a.js(24,5): error TS2322: Type 'null' is not assignable to type 'string | undefined'. -tests/cases/conformance/salsa/a.js(25,5): error TS2322: Type 'false' is not assignable to type 'string | undefined'. +tests/cases/conformance/salsa/a.js(24,5): error TS2322: Type 'null' is not assignable to type 'string'. +tests/cases/conformance/salsa/a.js(25,5): error TS2322: Type 'false' is not assignable to type 'string'. tests/cases/conformance/salsa/a.js(26,5): error TS2531: Object is possibly 'null'. -==== tests/cases/conformance/salsa/a.js (6 errors) ==== +==== tests/cases/conformance/salsa/a.js (7 errors) ==== function Installer () { // arg: number this.arg = 0 @@ -28,6 +29,8 @@ tests/cases/conformance/salsa/a.js(26,5): error TS2531: Object is possibly 'null this.unknown = 'hi' // ok this.newProperty = 1 // ok: number | boolean this.twice = undefined // ok + ~~~~~~~~~~ +!!! error TS2322: Type 'undefined' is not assignable to type 'string'. this.twice = 'hi' // ok } Installer.prototype.second = function () { @@ -38,10 +41,10 @@ tests/cases/conformance/salsa/a.js(26,5): error TS2531: Object is possibly 'null this.newProperty = false // ok this.twice = null // error ~~~~~~~~~~ -!!! error TS2322: Type 'null' is not assignable to type 'string | undefined'. +!!! error TS2322: Type 'null' is not assignable to type 'string'. this.twice = false // error ~~~~~~~~~~ -!!! error TS2322: Type 'false' is not assignable to type 'string | undefined'. +!!! error TS2322: Type 'false' is not assignable to type 'string'. this.twices.push(1) // error: Object is possibly null ~~~~~~~~~~~ !!! error TS2531: Object is possibly 'null'. diff --git a/tests/baselines/reference/typeFromJSConstructor.types b/tests/baselines/reference/typeFromJSConstructor.types index 38d736e5a688c..68bd498f0f265 100644 --- a/tests/baselines/reference/typeFromJSConstructor.types +++ b/tests/baselines/reference/typeFromJSConstructor.types @@ -80,16 +80,16 @@ Installer.prototype.first = function () { this.twice = undefined // ok >this.twice = undefined : undefined ->this.twice : string | undefined +>this.twice : string >this : this ->twice : string | undefined +>twice : string >undefined : undefined this.twice = 'hi' // ok >this.twice = 'hi' : "hi" ->this.twice : string | undefined +>this.twice : string >this : this ->twice : string | undefined +>twice : string >'hi' : "hi" } Installer.prototype.second = function () { @@ -124,16 +124,16 @@ Installer.prototype.second = function () { this.twice = null // error >this.twice = null : null ->this.twice : string | undefined +>this.twice : string >this : this ->twice : string | undefined +>twice : string >null : null this.twice = false // error >this.twice = false : false ->this.twice : string | undefined +>this.twice : string >this : this ->twice : string | undefined +>twice : string >false : false this.twices.push(1) // error: Object is possibly null diff --git a/tests/baselines/reference/typeFromPrototypeAssignment.types b/tests/baselines/reference/typeFromPrototypeAssignment.types index 1e8aee1e1badb..b6d501697a58a 100644 --- a/tests/baselines/reference/typeFromPrototypeAssignment.types +++ b/tests/baselines/reference/typeFromPrototypeAssignment.types @@ -8,9 +8,9 @@ var Multimap = function() { this._map = {}; >this._map = {} : {} ->this._map : {} +>this._map : any >this : this ->_map : {} +>_map : any >{} : {} this._map diff --git a/tests/baselines/reference/typeFromPrototypeAssignment2.types b/tests/baselines/reference/typeFromPrototypeAssignment2.types index 1a484ba8fc8bf..5f26d98200aca 100644 --- a/tests/baselines/reference/typeFromPrototypeAssignment2.types +++ b/tests/baselines/reference/typeFromPrototypeAssignment2.types @@ -13,9 +13,9 @@ this._map = {}; >this._map = {} : {} ->this._map : {} +>this._map : any >this : this ->_map : {} +>_map : any >{} : {} this._map From 0c9c76a14a8e0d61fe410e2633f52f8e1f68ce05 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 14 Apr 2020 10:34:28 -0700 Subject: [PATCH 05/10] Fix lint error --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6729a6c001542..82b149d8cc34a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7552,7 +7552,7 @@ namespace ts { const containerObjectType = getJSContainerObjectType(symbol.valueDeclaration, symbol, container); return containerObjectType || getWidenedLiteralType(checkExpressionCached(container)); } - let type = undefined; + let type; let definedInConstructor = false; let definedInMethod = false; // We use control flow analysis to determine the type of the property if the property qualifies as a constructor From 728d9cb9117255ae9caae9a8910acf221fe61681 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 15 Apr 2020 18:25:14 -0700 Subject: [PATCH 06/10] Only widen fresh literal types in CFA of assignment to auto-typed --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 82b149d8cc34a..e6f8f62fe3127 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -20150,7 +20150,7 @@ namespace ts { if (isEmptyArrayAssignment(node)) { return getEvolvingArrayType(neverType); } - const assignedType = getBaseTypeOfLiteralType(getInitialOrAssignedType(flow)); + const assignedType = getWidenedLiteralType(getInitialOrAssignedType(flow)); return isTypeAssignableTo(assignedType, declaredType) ? assignedType : anyArrayType; } if (declaredType.flags & TypeFlags.Union) { From fa6fee8ca41663a27774d4a17e114672af58f030 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 20 Apr 2020 20:33:11 -0700 Subject: [PATCH 07/10] Auto-typing for declared properties with no type annotation or initializer --- src/compiler/checker.ts | 64 +++++++++++++++++++++++------------------ src/compiler/types.ts | 1 - 2 files changed, 36 insertions(+), 29 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e6f8f62fe3127..4c4b795dc9a55 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7484,6 +7484,15 @@ namespace ts { return addOptionality(type, isOptional); } + if (noImplicitAny && isPropertyDeclaration(declaration)) { + // We are in noImplicitAny mode and have a property declaration with no type annotation or initializer. Use + // control flow analysis of this.xxx assignments the constructor to determine the type of the property. + const constructor = findConstructorDeclaration(declaration.parent); + return constructor ? getFlowTypeInConstructor(declaration.symbol, constructor) : + getModifierFlags(declaration) & ModifierFlags.Ambient ? getTypeOfPropertyInBaseClass(declaration.symbol) : + undefined; + } + if (isJsxAttribute(declaration)) { // if JSX attribute doesn't have initializer, by default the attribute will have boolean value of true. // I.e is sugar for @@ -7512,13 +7521,18 @@ namespace ts { getAssignmentDeclarationKind(declaration) === AssignmentDeclarationKind.ThisProperty && (declaration.left.kind !== SyntaxKind.ElementAccessExpression || isStringOrNumericLiteralLike((declaration.left).argumentExpression)) && !getAnnotatedTypeForAssignmentDeclaration(/*declaredType*/ undefined, declaration, symbol, declaration)); - links.typeOfPropertyInBaseClass = getTypeOfAssignmentDeclarationPropertyOfBaseType(symbol); } return links.isConstructorDeclaredProperty; } return false; } + function isAutoTypedProperty(symbol: Symbol) { + // A property is auto-typed in noImplicitAny mode when its declaration has no type annotation or initializer. + const declaration = symbol.valueDeclaration; + return noImplicitAny && declaration && isPropertyDeclaration(declaration) && !declaration.type && !declaration.initializer; + } + function getDeclaringConstructor(symbol: Symbol) { for (const declaration of symbol.declarations) { const container = getThisContainer(declaration, /*includeArrowFunctions*/ false); @@ -7528,17 +7542,22 @@ namespace ts { } } - function getTypeOfConstructorDeclaredProperty(symbol: Symbol) { - const constructor = getDeclaringConstructor(symbol)!; + function getFlowTypeInConstructor(symbol: Symbol, constructor: ConstructorDeclaration) { const reference = createPropertyAccess(createThis(), unescapeLeadingUnderscores(symbol.escapedName)); reference.expression.parent = reference; reference.parent = constructor; reference.flowNode = constructor.returnFlowNode; - const flowType = getFlowTypeOfReference(reference, autoType, getSymbolLinks(symbol).typeOfPropertyInBaseClass || undefinedType); + const flowType = getFlowTypeOfProperty(reference, symbol); if (noImplicitAny && (flowType === autoType || flowType === autoArrayType)) { error(symbol.valueDeclaration, Diagnostics.Member_0_implicitly_has_an_1_type, symbolToString(symbol), typeToString(flowType)); } - return convertAutoToAny(flowType); + // We don't infer a type if assignments are only null or undefined. + return everyType(flowType, isNullableType) ? undefined : convertAutoToAny(flowType); + } + + function getFlowTypeOfProperty(reference: Node, prop: Symbol | undefined) { + const initialType = prop && (!isAutoTypedProperty(prop) || getModifierFlags(prop.valueDeclaration) & ModifierFlags.Ambient) && getTypeOfPropertyInBaseClass(prop) || undefinedType; + return getFlowTypeOfReference(reference, autoType, initialType); } function getWidenedTypeForAssignmentDeclaration(symbol: Symbol, resolvedSymbol?: Symbol) { @@ -7558,10 +7577,7 @@ namespace ts { // We use control flow analysis to determine the type of the property if the property qualifies as a constructor // declared property and the resulting control flow type isn't just undefined or null. if (isConstructorDeclaredProperty(symbol)) { - const controlFlowType = getTypeOfConstructorDeclaredProperty(symbol); - if (!everyType(controlFlowType, isNullableType)) { - type = controlFlowType; - } + type = getFlowTypeInConstructor(symbol, getDeclaringConstructor(symbol)!); } if (!type) { let jsdocType: Type | undefined; @@ -7600,7 +7616,7 @@ namespace ts { let constructorTypes = definedInConstructor ? getConstructorDefinedThisAssignmentTypes(types!, symbol.declarations) : undefined; // use only the constructor types unless they were only assigned null | undefined (including widening variants) if (definedInMethod) { - const propType = getTypeOfAssignmentDeclarationPropertyOfBaseType(symbol); + const propType = getTypeOfPropertyInBaseClass(symbol); if (propType) { (constructorTypes || (constructorTypes = [])).push(propType); definedInConstructor = true; @@ -7753,21 +7769,6 @@ namespace ts { }); } - /** check for definition in base class if any declaration is in a class */ - function getTypeOfAssignmentDeclarationPropertyOfBaseType(property: Symbol) { - const parentDeclaration = forEach(property.declarations, d => { - const parent = getThisContainer(d, /*includeArrowFunctions*/ false).parent; - return isClassLike(parent) && parent; - }); - if (parentDeclaration) { - const classType = getDeclaredTypeOfSymbol(getSymbolOfNode(parentDeclaration)) as InterfaceType; - const baseClassType = classType && getBaseTypes(classType)[0]; - if (baseClassType) { - return getTypeOfPropertyOfType(baseClassType, property.escapedName); - } - } - } - // Return the type implied by a binding pattern element. This is the type of the initializer of the element if // one is present. Otherwise, if the element is itself a binding pattern, it is the type implied by the binding // pattern. Otherwise, it is the type any. @@ -17339,7 +17340,14 @@ namespace ts { // Return the declaring class type of a property or undefined if property not declared in class function getDeclaringClass(prop: Symbol) { - return prop.parent && prop.parent.flags & SymbolFlags.Class ? getDeclaredTypeOfSymbol(getParentOfSymbol(prop)!) : undefined; + return prop.parent && prop.parent.flags & SymbolFlags.Class ? getDeclaredTypeOfSymbol(getParentOfSymbol(prop)!) : undefined; + } + + // Return the inherited type of the given property or undefined if property doesn't exist in a base class. + function getTypeOfPropertyInBaseClass(property: Symbol) { + const classType = getDeclaringClass(property); + const baseClassType = classType && getBaseTypes(classType)[0]; + return baseClassType && getTypeOfPropertyOfType(baseClassType, property.escapedName); } // Return true if some underlying source property is declared in a class that derives @@ -24087,7 +24095,7 @@ namespace ts { } function isThisPropertyAccessInConstructor(node: ElementAccessExpression | PropertyAccessExpression | QualifiedName, prop: Symbol) { - return isThisProperty(node) && isConstructorDeclaredProperty(prop) && getThisContainer(node, /*includeArrowFunctions*/ true) === getDeclaringConstructor(prop); + return isThisProperty(node) && (isAutoTypedProperty(prop) || isConstructorDeclaredProperty(prop)) && getThisContainer(node, /*includeArrowFunctions*/ true) === getDeclaringConstructor(prop); } function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, leftType: Type, right: Identifier | PrivateIdentifier) { @@ -24180,7 +24188,7 @@ namespace ts { return propType; } if (propType === autoType) { - return getFlowTypeOfReference(node, autoType, prop && getSymbolLinks(prop).typeOfPropertyInBaseClass || undefinedType); + return getFlowTypeOfProperty(node, prop); } // If strict null checks and strict property initialization checks are enabled, if we have // a this.xxx property access, if the property is an instance property without an initializer, diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 8604f35947d4e..0b67aea7f99d7 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4157,7 +4157,6 @@ namespace ts { cjsExportMerged?: Symbol; // Version of the symbol with all non export= exports merged with the export= target typeOnlyDeclaration?: TypeOnlyCompatibleAliasDeclaration | false; // First resolved alias declaration that makes the symbol only usable in type constructs isConstructorDeclaredProperty?: boolean; // Property declared through 'this.x = ...' assignment in constructor - typeOfPropertyInBaseClass?: Type; // Type of constructor declared property in base class } /* @internal */ From e6d7607539e2757ddf38469b1a2b4782c9bd0c83 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 20 Apr 2020 20:44:02 -0700 Subject: [PATCH 08/10] Add optionality if declaration includes '?' modifier --- src/compiler/checker.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4c4b795dc9a55..dd7cd08f56de4 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7488,9 +7488,10 @@ namespace ts { // We are in noImplicitAny mode and have a property declaration with no type annotation or initializer. Use // control flow analysis of this.xxx assignments the constructor to determine the type of the property. const constructor = findConstructorDeclaration(declaration.parent); - return constructor ? getFlowTypeInConstructor(declaration.symbol, constructor) : + const type = constructor ? getFlowTypeInConstructor(declaration.symbol, constructor) : getModifierFlags(declaration) & ModifierFlags.Ambient ? getTypeOfPropertyInBaseClass(declaration.symbol) : undefined; + return type && addOptionality(type, isOptional); } if (isJsxAttribute(declaration)) { From 33cbb82a37e677e9e272e5963e3a51939df471da Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 21 Apr 2020 09:38:26 -0700 Subject: [PATCH 09/10] Always use CFA for properties with no initializer in .js files --- src/compiler/checker.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index dd7cd08f56de4..f5ec5c32a7854 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7484,9 +7484,9 @@ namespace ts { return addOptionality(type, isOptional); } - if (noImplicitAny && isPropertyDeclaration(declaration)) { - // We are in noImplicitAny mode and have a property declaration with no type annotation or initializer. Use - // control flow analysis of this.xxx assignments the constructor to determine the type of the property. + if (isPropertyDeclaration(declaration) && (noImplicitAny || isInJSFile(declaration))) { + // We have a property declaration with no type annotation or initializer, in noImplicitAny mode or a .js file. + // Use control flow analysis of this.xxx assignments the constructor to determine the type of the property. const constructor = findConstructorDeclaration(declaration.parent); const type = constructor ? getFlowTypeInConstructor(declaration.symbol, constructor) : getModifierFlags(declaration) & ModifierFlags.Ambient ? getTypeOfPropertyInBaseClass(declaration.symbol) : From ab993c2b48e60379c991258c504b5d2ea55832dc Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 21 Apr 2020 09:49:11 -0700 Subject: [PATCH 10/10] Small fix --- src/compiler/checker.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f5ec5c32a7854..b6afc5582d269 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7529,9 +7529,11 @@ namespace ts { } function isAutoTypedProperty(symbol: Symbol) { - // A property is auto-typed in noImplicitAny mode when its declaration has no type annotation or initializer. + // A property is auto-typed when its declaration has no type annotation or initializer and we're in + // noImplicitAny mode or a .js file. const declaration = symbol.valueDeclaration; - return noImplicitAny && declaration && isPropertyDeclaration(declaration) && !declaration.type && !declaration.initializer; + return declaration && isPropertyDeclaration(declaration) && !getEffectiveTypeAnnotationNode(declaration) && + !declaration.initializer && (noImplicitAny || isInJSFile(declaration)); } function getDeclaringConstructor(symbol: Symbol) { pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy