diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f185a0891f5..d8b7c1fe681 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3055,7 +3055,9 @@ namespace ts { return addOptionality(getTypeFromTypeNode(declaration.type), /*optional*/ declaration.questionToken && includeOptionality); } - if (declaration.kind === SyntaxKind.VariableDeclaration && !(getCombinedNodeFlags(declaration) & NodeFlags.Const) && !declaration.initializer) { + // Use control flow type inference for non-ambient, non-exported var or let variables with no initializer + if (declaration.kind === SyntaxKind.VariableDeclaration && !isBindingPattern(declaration.name) && !declaration.initializer && + !(getCombinedNodeFlags(declaration) & (NodeFlags.Export | NodeFlags.Const)) && !isInAmbientContext(declaration)) { return autoType; } @@ -3226,8 +3228,11 @@ namespace ts { if (symbol.flags & SymbolFlags.Prototype) { return links.type = getTypeOfPrototypeProperty(symbol); } - // Handle catch clause variables const declaration = symbol.valueDeclaration; + if (!declaration && symbol.openType) { + return getFlowTypeOfReference(symbol.propAccess, autoType, /*assumeInitialized*/ false, symbol.openType.flowNode, symbol.openType.flowContainer); + } + // Handle catch clause variables if (declaration.parent.kind === SyntaxKind.CatchClause) { return links.type = anyType; } @@ -4239,7 +4244,6 @@ namespace ts { } function resolveAnonymousTypeMembers(type: AnonymousType) { - const symbol = type.symbol; if (type.target) { const members = createInstantiatedSymbolTable(getPropertiesOfObjectType(type.target), type.mapper, /*mappingThisOnly*/ false); const callSignatures = instantiateList(getSignaturesOfType(type.target, SignatureKind.Call), type.mapper, instantiateSignature); @@ -4247,44 +4251,50 @@ namespace ts { const stringIndexInfo = instantiateIndexInfo(getIndexInfoOfType(type.target, IndexKind.String), type.mapper); const numberIndexInfo = instantiateIndexInfo(getIndexInfoOfType(type.target, IndexKind.Number), type.mapper); setObjectTypeMembers(type, members, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo); + return; } - else if (symbol.flags & SymbolFlags.TypeLiteral) { + if (type.assignedMembers) { + setObjectTypeMembers(type, type.assignedMembers, emptyArray, emptyArray, undefined, undefined); + type.assignedMembers = undefined; + return; + } + const symbol = type.symbol; + if (symbol.flags & SymbolFlags.TypeLiteral) { const members = symbol.members; const callSignatures = getSignaturesOfSymbol(members["__call"]); const constructSignatures = getSignaturesOfSymbol(members["__new"]); const stringIndexInfo = getIndexInfoOfSymbol(symbol, IndexKind.String); const numberIndexInfo = getIndexInfoOfSymbol(symbol, IndexKind.Number); setObjectTypeMembers(type, members, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo); + return; } - else { - // Combinations of function, class, enum and module - let members = emptySymbols; - let constructSignatures: Signature[] = emptyArray; - if (symbol.flags & SymbolFlags.HasExports) { - members = getExportsOfSymbol(symbol); + // Combinations of function, class, enum and module + let members = emptySymbols; + let constructSignatures: Signature[] = emptyArray; + if (symbol.flags & SymbolFlags.HasExports) { + members = getExportsOfSymbol(symbol); + } + if (symbol.flags & SymbolFlags.Class) { + const classType = getDeclaredTypeOfClassOrInterface(symbol); + constructSignatures = getSignaturesOfSymbol(symbol.members["__constructor"]); + if (!constructSignatures.length) { + constructSignatures = getDefaultConstructSignatures(classType); } - if (symbol.flags & SymbolFlags.Class) { - const classType = getDeclaredTypeOfClassOrInterface(symbol); - constructSignatures = getSignaturesOfSymbol(symbol.members["__constructor"]); - if (!constructSignatures.length) { - constructSignatures = getDefaultConstructSignatures(classType); - } - const baseConstructorType = getBaseConstructorTypeOfClass(classType); - if (baseConstructorType.flags & TypeFlags.ObjectType) { - members = createSymbolTable(getNamedMembers(members)); - addInheritedMembers(members, getPropertiesOfObjectType(baseConstructorType)); - } - } - const numberIndexInfo = symbol.flags & SymbolFlags.Enum ? enumNumberIndexInfo : undefined; - setObjectTypeMembers(type, members, emptyArray, constructSignatures, undefined, numberIndexInfo); - // We resolve the members before computing the signatures because a signature may use - // typeof with a qualified name expression that circularly references the type we are - // in the process of resolving (see issue #6072). The temporarily empty signature list - // will never be observed because a qualified name can't reference signatures. - if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method)) { - (type).callSignatures = getSignaturesOfSymbol(symbol); + const baseConstructorType = getBaseConstructorTypeOfClass(classType); + if (baseConstructorType.flags & TypeFlags.ObjectType) { + members = createSymbolTable(getNamedMembers(members)); + addInheritedMembers(members, getPropertiesOfObjectType(baseConstructorType)); } } + const numberIndexInfo = symbol.flags & SymbolFlags.Enum ? enumNumberIndexInfo : undefined; + setObjectTypeMembers(type, members, emptyArray, constructSignatures, undefined, numberIndexInfo); + // We resolve the members before computing the signatures because a signature may use + // typeof with a qualified name expression that circularly references the type we are + // in the process of resolving (see issue #6072). The temporarily empty signature list + // will never be observed because a qualified name can't reference signatures. + if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method)) { + (type).callSignatures = getSignaturesOfSymbol(symbol); + } } function resolveStructuredTypeMembers(type: ObjectType): ResolvedType { @@ -7218,7 +7228,8 @@ namespace ts { } function getNonNullableType(type: Type): Type { - return strictNullChecks ? getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type; + const nonNullableType = strictNullChecks ? getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type; + return nonNullableType !== neverType ? nonNullableType : type; } /** @@ -8197,20 +8208,54 @@ namespace ts { return incomplete ? { flags: 0, type } : type; } - function getFlowTypeOfReference(reference: Node, declaredType: Type, assumeInitialized: boolean, flowContainer: Node) { + function isEmptyObjectLiteralType(type: Type): boolean { + return type.flags & TypeFlags.ObjectLiteral && getPropertiesOfType(type).length === 0; + } + + function createOpenType(flowNode: FlowNode, flowContainer: Node): Type { + const result = createObjectType(TypeFlags.Anonymous); + result.assignedMembers = createMap(); + result.flowNode = flowNode; + result.flowContainer = flowContainer; + return result; + } + + function isOpenType(type: Type): boolean { + return !!(type.flags & TypeFlags.Anonymous && (type).assignedMembers); + } + + function isOpenTypeProperty(symbol: Symbol): boolean { + return !!symbol.openType; + } + + function expandOpenType(type: AnonymousType, propAccess: PropertyAccessExpression) { + const name = propAccess.name.text; + const prop = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, name); + prop.openType = type; + prop.propAccess = propAccess; + type.assignedMembers[name] = prop; + return type; + } + + function getFlowTypeOfReference(reference: Node, declaredType: Type, assumeInitialized: boolean, flowNode: FlowNode, flowContainer: Node) { let key: string; - if (!reference.flowNode || assumeInitialized && !(declaredType.flags & TypeFlags.Narrowable)) { + if (!flowNode || assumeInitialized && !(declaredType.flags & TypeFlags.Narrowable)) { return declaredType; } const initialType = assumeInitialized ? declaredType : declaredType === autoType ? undefinedType : includeFalsyTypes(declaredType, TypeFlags.Undefined); const visitedFlowStart = visitedFlowCount; - const result = getTypeFromFlowType(getTypeAtFlowNode(reference.flowNode)); + const result = getTypeFromFlowType(getTypeAtFlowNode(flowNode)); visitedFlowCount = visitedFlowStart; if (reference.parent.kind === SyntaxKind.NonNullExpression && getTypeWithFacts(result, TypeFacts.NEUndefinedOrNull) === neverType) { return declaredType; } + if (isOpenType(result)) { + if (reference.parent.kind === SyntaxKind.PropertyAccessExpression && isAssignmentTarget(reference.parent)) { + return anyType; + } + } return result; function getTypeAtFlowNode(flow: FlowNode): FlowType { @@ -8279,16 +8324,24 @@ namespace ts { // only need to evaluate the assigned type if the declared type is a union type. if (isMatchingReference(reference, node)) { const type = getInitialOrAssignedType(node); - return declaredType === autoType || type === neverType ? type : + return type === neverType ? neverType : + declaredType === autoType ? isEmptyObjectLiteralType(type) ? createOpenType(flowNode, flowContainer) : type : declaredType.flags & TypeFlags.Union ? getAssignmentReducedType(declaredType, type) : declaredType; } + // See if we're expanding an open type. + if (declaredType === autoType && node.kind === SyntaxKind.PropertyAccessExpression && isMatchingReference(reference, (node).expression)) { + const type = getTypeFromFlowType(getTypeAtFlowNode(flow.antecedent)); + if (isOpenType(type)) { + return expandOpenType(type, node); + } + } // We didn't have a direct match. However, if the reference is a dotted name, this // may be an assignment to a left hand part of the reference. For example, for a // reference 'x.y.z', we may be at an assignment to 'x.y' or 'x'. In that case, - // return the declared type. + // return the initial type. if (containsMatchingReference(reference, node)) { - return declaredType; + return initialType; } // Assignment doesn't affect reference return undefined; @@ -8858,7 +8911,7 @@ namespace ts { const assumeInitialized = flowContainer !== declarationContainer || isParameter || type !== autoType && (!strictNullChecks || (type.flags & TypeFlags.Any) !== 0) || isInAmbientContext(declaration); - const flowType = getFlowTypeOfReference(node, type, assumeInitialized, flowContainer); + const flowType = getFlowTypeOfReference(node, type, assumeInitialized, node.flowNode, flowContainer); // A variable is considered uninitialized when it is possible to analyze the entire control flow graph // from declaration to use, and when the variable's declared type doesn't include undefined but the // control flow based type does include undefined. @@ -9120,7 +9173,7 @@ namespace ts { if (isClassLike(container.parent)) { const symbol = getSymbolOfNode(container.parent); const type = container.flags & NodeFlags.Static ? getTypeOfSymbol(symbol) : (getDeclaredTypeOfSymbol(symbol)).thisType; - return getFlowTypeOfReference(node, type, /*assumeInitialized*/ true, /*flowContainer*/ undefined); + return getFlowTypeOfReference(node, type, /*assumeInitialized*/ true, node.flowNode, /*flowContainer*/ undefined); } if (isInJavaScriptFile(node)) { @@ -10714,15 +10767,14 @@ namespace ts { function checkNonNullExpression(node: Expression | QualifiedName) { const type = checkExpression(node); - if (strictNullChecks) { - const kind = getFalsyFlags(type) & TypeFlags.Nullable; - if (kind) { - error(node, kind & TypeFlags.Undefined ? kind & TypeFlags.Null ? - Diagnostics.Object_is_possibly_null_or_undefined : - Diagnostics.Object_is_possibly_undefined : - Diagnostics.Object_is_possibly_null); - } - return getNonNullableType(type); + const kind = getFalsyFlags(type) & TypeFlags.Nullable; + if (kind) { + error(node, kind & TypeFlags.Undefined ? kind & TypeFlags.Null ? + Diagnostics.Object_is_possibly_null_or_undefined : + Diagnostics.Object_is_possibly_undefined : + Diagnostics.Object_is_possibly_null); + const nonNullableType = getNonNullableType(type); + return getFalsyFlags(nonNullableType) & TypeFlags.Nullable ? unknownType : nonNullableType; } return type; } @@ -10785,7 +10837,7 @@ namespace ts { !(prop.flags & SymbolFlags.Method && propType.flags & TypeFlags.Union)) { return propType; } - return getFlowTypeOfReference(node, propType, /*assumeInitialized*/ true, /*flowContainer*/ undefined); + return getFlowTypeOfReference(node, propType, /*assumeInitialized*/ true, node.flowNode, /*flowContainer*/ undefined); } function isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName, propertyName: string): boolean { @@ -15539,6 +15591,10 @@ namespace ts { } } + function convertAutoToAny(type: Type) { + return type === autoType ? anyType : type; + } + // Check variable, parameter, or property declaration function checkVariableLikeDeclaration(node: VariableLikeDeclaration) { checkDecorators(node); @@ -15589,7 +15645,7 @@ namespace ts { return; } const symbol = getSymbolOfNode(node); - const type = getTypeOfVariableOrParameterOrProperty(symbol); + const type = convertAutoToAny(getTypeOfVariableOrParameterOrProperty(symbol)); if (node === symbol.valueDeclaration) { // Node is the primary declaration of the symbol, just validate the initializer // Don't validate for-in initializer as it is already an error @@ -15601,7 +15657,7 @@ namespace ts { else { // Node is a secondary declaration, check that type is identical to primary declaration and check that // initializer is consistent with type associated with the node - const declarationType = getWidenedTypeForVariableLikeDeclaration(node); + const declarationType = convertAutoToAny(getWidenedTypeForVariableLikeDeclaration(node)); if (type !== unknownType && declarationType !== unknownType && !isTypeIdenticalTo(type, declarationType)) { error(node.name, Diagnostics.Subsequent_variable_declarations_must_have_the_same_type_Variable_0_must_be_of_type_1_but_here_has_type_2, declarationNameToString(node.name), typeToString(type), typeToString(declarationType)); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 7594d2c3fb8..f73fd86b853 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2164,6 +2164,8 @@ namespace ts { /* @internal */ isReferenced?: boolean; // True if the symbol is referenced elsewhere /* @internal */ isReplaceableByMethod?: boolean; // Can this Javascript class property be replaced by a method symbol? /* @internal */ isAssigned?: boolean; // True if the symbol is a parameter with assignments + /* @internal */ openType?: AnonymousType; // The open type to which this property belongs + /* @internal */ propAccess?: PropertyAccessExpression; // Property access expression for open type property } /* @internal */ @@ -2407,6 +2409,9 @@ namespace ts { export interface AnonymousType extends ObjectType { target?: AnonymousType; // Instantiation target mapper?: TypeMapper; // Instantiation mapper + assignedMembers?: SymbolTable; // Open type members + flowNode?: FlowNode; // Open type control flow node + flowContainer?: Node; // Open type control flow container } /* @internal */