From 9ca2569363809e3bd7aaf8bb817ec5a7020d231b Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 6 Oct 2016 11:04:40 -0700 Subject: [PATCH 01/22] Use control flow analysis for variables initialized with [] --- src/compiler/binder.ts | 21 +++ src/compiler/checker.ts | 220 +++++++++++++++++++++------ src/compiler/diagnosticMessages.json | 2 +- src/compiler/types.ts | 14 +- 4 files changed, 211 insertions(+), 46 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 49434e80abab4..624ea269d899b 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -760,6 +760,15 @@ namespace ts { }; } + function createFlowArrayMutation(antecedent: FlowNode, node: Expression): FlowNode { + setFlowNodeReferenced(antecedent); + return { + flags: FlowFlags.ArrayMutation, + antecedent, + node + }; + } + function finishFlowLabel(flow: FlowLabel): FlowNode { const antecedents = flow.antecedents; if (!antecedents) { @@ -1136,6 +1145,12 @@ namespace ts { forEachChild(node, bind); if (operator === SyntaxKind.EqualsToken && !isAssignmentTarget(node)) { bindAssignmentTargetFlow(node.left); + if (node.left.kind === SyntaxKind.ElementAccessExpression) { + const elementAccess = node.left; + if (isNarrowableReference(elementAccess.expression)) { + currentFlow = createFlowArrayMutation(currentFlow, elementAccess.expression); + } + } } } } @@ -1196,6 +1211,12 @@ namespace ts { else { forEachChild(node, bind); } + if (node.expression.kind === SyntaxKind.PropertyAccessExpression) { + const propertyAccess = node.expression; + if (isNarrowableReference(propertyAccess.expression) && propertyAccess.name.text === "push") { + currentFlow = createFlowArrayMutation(currentFlow, propertyAccess.expression); + } + } } function getContainerFlags(node: Node): ContainerFlags { diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 887a406bb8c35..eff5296754475 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -114,6 +114,7 @@ namespace ts { const intersectionTypes = createMap(); const stringLiteralTypes = createMap(); const numericLiteralTypes = createMap(); + const evolvingArrayTypes: AnonymousType[] = []; const unknownSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, "unknown"); const resolvingSymbol = createSymbol(SymbolFlags.Transient, "__resolving__"); @@ -175,6 +176,7 @@ namespace ts { let globalBooleanType: ObjectType; let globalRegExpType: ObjectType; let anyArrayType: Type; + let autoArrayType: Type; let anyReadonlyArrayType: Type; // The library files are only loaded when the feature is used. @@ -3051,9 +3053,14 @@ namespace ts { return undefined; } - function isAutoVariableInitializer(initializer: Expression) { - const expr = initializer && skipParentheses(initializer); - return !expr || expr.kind === SyntaxKind.NullKeyword || expr.kind === SyntaxKind.Identifier && getResolvedSymbol(expr) === undefinedSymbol; + function isNullOrUndefined(node: Expression) { + const expr = skipParentheses(node); + return expr.kind === SyntaxKind.NullKeyword || expr.kind === SyntaxKind.Identifier && getResolvedSymbol(expr) === undefinedSymbol; + } + + function isEmptyArrayLiteral(node: Expression) { + const expr = skipParentheses(node); + return expr.kind === SyntaxKind.ArrayLiteralExpression && (expr).elements.length === 0; } function addOptionality(type: Type, optional: boolean): Type { @@ -3094,12 +3101,18 @@ namespace ts { return addOptionality(getTypeFromTypeNode(declaration.type), /*optional*/ declaration.questionToken && includeOptionality); } - // Use control flow type inference for non-ambient, non-exported var or let variables with no initializer - // or a 'null' or 'undefined' initializer. if (declaration.kind === SyntaxKind.VariableDeclaration && !isBindingPattern(declaration.name) && - !(getCombinedNodeFlags(declaration) & NodeFlags.Const) && !(getCombinedModifierFlags(declaration) & ModifierFlags.Export) && - !isInAmbientContext(declaration) && isAutoVariableInitializer(declaration.initializer)) { - return autoType; + !(getCombinedModifierFlags(declaration) & ModifierFlags.Export) && !isInAmbientContext(declaration)) { + // Use control flow tracked 'any' type for non-ambient, non-exported var or let variables with no + // initializer or a 'null' or 'undefined' initializer. + if (!(getCombinedNodeFlags(declaration) & NodeFlags.Const) && (!declaration.initializer || isNullOrUndefined(declaration.initializer))) { + return autoType; + } + // Use control flow tracked 'any[]' type for non-ambient, non-exported variables with an empty array + // literal initializer. + if (declaration.initializer && isEmptyArrayLiteral(declaration.initializer)) { + return autoArrayType; + } } if (declaration.kind === SyntaxKind.Parameter) { @@ -8363,6 +8376,11 @@ namespace ts { getAssignedType(node); } + function isEmptyArrayAssignment(node: VariableDeclaration | BindingElement | Expression) { + return node.kind === SyntaxKind.VariableDeclaration && (node).initializer && isEmptyArrayLiteral((node).initializer) || + node.kind !== SyntaxKind.BindingElement && node.parent.kind === SyntaxKind.BinaryExpression && isEmptyArrayLiteral((node.parent).right); + } + function getReferenceCandidate(node: Expression): Expression { switch (node.kind) { case SyntaxKind.ParenthesizedExpression: @@ -8441,21 +8459,94 @@ namespace ts { return incomplete ? { flags: 0, type } : type; } + // An evolving array type tracks the element types that have so far been seen in an + // 'x.push(value)' or 'x[n] = value' operation along the control flow graph. Evolving + // array types are ultimately converted into manifest array types (using getFinalArrayType) + // and never escape the getFlowTypeOfReference function. + function createEvolvingArrayType(elementType: Type): AnonymousType { + const result = createObjectType(TypeFlags.Anonymous); + result.elementType = elementType; + return result; + } + + function getEvolvingArrayType(elementType: Type): AnonymousType { + return evolvingArrayTypes[elementType.id] || (evolvingArrayTypes[elementType.id] = createEvolvingArrayType(elementType)); + } + + // When adding evolving array element types we do not perform subtype reduction. Instead, + // we defer subtype reduction until the evolving array type is finalized into a manifest + // array type. + function addEvolvingArrayElementType(evolvingArrayType: AnonymousType, node: Expression): AnonymousType { + const elementType = getBaseTypeOfLiteralType(checkExpression(node)); + return isTypeSubsetOf(elementType, evolvingArrayType.elementType) ? evolvingArrayType : getEvolvingArrayType(getUnionType([evolvingArrayType.elementType, elementType])); + } + + function isEvolvingArrayType(type: Type) { + return type.flags & TypeFlags.Anonymous && !!(type).elementType; + } + + // We perform subtype reduction upon obtaining the final array type from an evolving array type. + function getFinalArrayType(evolvingArrayType: AnonymousType): Type { + return evolvingArrayType.finalArrayType || (evolvingArrayType.finalArrayType = createArrayType(getUnionType([evolvingArrayType.elementType], /*subtypeReduction*/ true))); + } + + function finalizeEvolvingArrayType(type: Type): Type { + return isEvolvingArrayType(type) ? getFinalArrayType(type) : type; + } + + function getElementTypeOfEvolvingArrayType(evolvingArrayType: AnonymousType) { + return evolvingArrayType.elementType; + } + + // At flow control branch or loop junctions, if the type along every antecedent code path + // is an evolving array type, we construct a combined evolving array type. Otherwise we + // finalize all evolving array types. + function getUnionOrEvolvingArrayType(types: Type[], subtypeReduction: boolean) { + return types.length && every(types, isEvolvingArrayType) ? + getEvolvingArrayType(getUnionType(map(types, getElementTypeOfEvolvingArrayType))) : + getUnionType(map(types, finalizeEvolvingArrayType), subtypeReduction); + } + + // Return true if the given node is 'x' in an 'x.push(value)' operation. + function isPushCallTarget(node: Node) { + return node.parent.kind === SyntaxKind.PropertyAccessExpression && + (node.parent).name.text === "push" && + node.parent.parent.kind === SyntaxKind.CallExpression; + } + + // Return true if the given node is 'x' in an 'x[n] = value' operation, where 'n' is an + // expression of type any, undefined, or a number-like type. + function isElementAssignmentTarget(node: Node) { + const parent = node.parent; + return parent.kind === SyntaxKind.ElementAccessExpression && + (parent).expression === node && + parent.parent.kind === SyntaxKind.BinaryExpression && + (parent.parent).operatorToken.kind === SyntaxKind.EqualsToken && + (parent.parent).left === parent && + !isAssignmentTarget(parent.parent) && + isTypeAnyOrAllConstituentTypesHaveKind(checkExpression((parent).argumentExpression), TypeFlags.NumberLike | TypeFlags.Undefined); + } + function getFlowTypeOfReference(reference: Node, declaredType: Type, assumeInitialized: boolean, flowContainer: Node) { let key: string; if (!reference.flowNode || assumeInitialized && !(declaredType.flags & TypeFlags.Narrowable)) { return declaredType; } const initialType = assumeInitialized ? declaredType : - declaredType === autoType ? undefinedType : + declaredType === autoType || declaredType === autoArrayType ? undefinedType : includeFalsyTypes(declaredType, TypeFlags.Undefined); const visitedFlowStart = visitedFlowCount; - const result = getTypeFromFlowType(getTypeAtFlowNode(reference.flowNode)); + const evolvedType = getTypeFromFlowType(getTypeAtFlowNode(reference.flowNode)); visitedFlowCount = visitedFlowStart; - if (reference.parent.kind === SyntaxKind.NonNullExpression && getTypeWithFacts(result, TypeFacts.NEUndefinedOrNull).flags & TypeFlags.Never) { + // When the reference is 'x' in an 'x.push(value)' or 'x[n] = value' operation, we give type + // 'any[]' to 'x' instead of using the type determed by control flow analysis such that new + // element types are not considered errors. + const isEvolvingArrayInferenceTarget = isEvolvingArrayType(evolvedType) && (isPushCallTarget(reference) || isElementAssignmentTarget(reference)); + const resultType = isEvolvingArrayInferenceTarget ? anyArrayType : finalizeEvolvingArrayType(evolvedType); + if (reference.parent.kind === SyntaxKind.NonNullExpression && getTypeWithFacts(resultType, TypeFacts.NEUndefinedOrNull).flags & TypeFlags.Never) { return declaredType; } - return result; + return resultType; function getTypeAtFlowNode(flow: FlowNode): FlowType { while (true) { @@ -8492,6 +8583,13 @@ namespace ts { getTypeAtFlowBranchLabel(flow) : getTypeAtFlowLoopLabel(flow); } + else if (flow.flags & FlowFlags.ArrayMutation) { + type = getTypeAtFlowArrayMutation(flow); + if (!type) { + flow = (flow).antecedent; + continue; + } + } else if (flow.flags & FlowFlags.Start) { // Check if we should continue with the control flow of the containing function. const container = (flow).container; @@ -8504,8 +8602,8 @@ namespace ts { } else { // Unreachable code errors are reported in the binding phase. Here we - // simply return the declared type to reduce follow-on errors. - type = declaredType; + // simply return the non-auto declared type to reduce follow-on errors. + type = convertAutoToAny(declaredType); } if (flow.flags & FlowFlags.Shared) { // Record visited node and the associated type in the cache. @@ -8526,9 +8624,17 @@ namespace ts { const flowType = getTypeAtFlowNode(flow.antecedent); return createFlowType(getBaseTypeOfLiteralType(getTypeFromFlowType(flowType)), isIncomplete(flowType)); } - return declaredType === autoType ? getBaseTypeOfLiteralType(getInitialOrAssignedType(node)) : - declaredType.flags & TypeFlags.Union ? getAssignmentReducedType(declaredType, getInitialOrAssignedType(node)) : - declaredType; + if (declaredType === autoType || declaredType === autoArrayType) { + if (isEmptyArrayAssignment(node)) { + return getEvolvingArrayType(neverType); + } + const assignedType = getBaseTypeOfLiteralType(getInitialOrAssignedType(node)); + return isTypeAssignableTo(assignedType, declaredType) ? assignedType : anyArrayType; + } + if (declaredType.flags & TypeFlags.Union) { + return getAssignmentReducedType(declaredType, getInitialOrAssignedType(node)); + } + return declaredType; } // 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 @@ -8541,24 +8647,51 @@ namespace ts { return undefined; } - function getTypeAtFlowCondition(flow: FlowCondition): FlowType { - const flowType = getTypeAtFlowNode(flow.antecedent); - let type = getTypeFromFlowType(flowType); - if (!(type.flags & TypeFlags.Never)) { - // If we have an antecedent type (meaning we're reachable in some way), we first - // attempt to narrow the antecedent type. If that produces the never type, and if - // the antecedent type is incomplete (i.e. a transient type in a loop), then we - // take the type guard as an indication that control *could* reach here once we - // have the complete type. We proceed by switching to the silent never type which - // doesn't report errors when operators are applied to it. Note that this is the - // *only* place a silent never type is ever generated. - const assumeTrue = (flow.flags & FlowFlags.TrueCondition) !== 0; - type = narrowType(type, flow.expression, assumeTrue); - if (type.flags & TypeFlags.Never && isIncomplete(flowType)) { - type = silentNeverType; + function getTypeAtFlowArrayMutation(flow: FlowArrayMutation): FlowType { + const node = flow.node; + if (isMatchingReference(reference, node)) { + const flowType = getTypeAtFlowNode(flow.antecedent); + const type = getTypeFromFlowType(flowType); + if (isEvolvingArrayType(type)) { + const parent = node.parent; + let evolvedType = type; + if (parent.kind === SyntaxKind.PropertyAccessExpression) { + for (const arg of (parent.parent).arguments) { + evolvedType = addEvolvingArrayElementType(evolvedType, arg); + } + } + else if (isTypeAnyOrAllConstituentTypesHaveKind(checkExpression((parent).argumentExpression), TypeFlags.NumberLike)) { + evolvedType = addEvolvingArrayElementType(evolvedType, (parent.parent).right); + } + return evolvedType === type ? flowType : createFlowType(evolvedType, isIncomplete(flowType)); } + return flowType; } - return createFlowType(type, isIncomplete(flowType)); + return undefined; + } + + function getTypeAtFlowCondition(flow: FlowCondition): FlowType { + const flowType = getTypeAtFlowNode(flow.antecedent); + const type = getTypeFromFlowType(flowType); + if (type.flags & TypeFlags.Never) { + return flowType; + } + // If we have an antecedent type (meaning we're reachable in some way), we first + // attempt to narrow the antecedent type. If that produces the never type, and if + // the antecedent type is incomplete (i.e. a transient type in a loop), then we + // take the type guard as an indication that control *could* reach here once we + // have the complete type. We proceed by switching to the silent never type which + // doesn't report errors when operators are applied to it. Note that this is the + // *only* place a silent never type is ever generated. + const assumeTrue = (flow.flags & FlowFlags.TrueCondition) !== 0; + const nonEvolvingType = finalizeEvolvingArrayType(type); + const narrowedType = narrowType(nonEvolvingType, flow.expression, assumeTrue); + if (narrowedType === nonEvolvingType) { + return flowType; + } + const incomplete = isIncomplete(flowType); + const resultType = incomplete && narrowedType.flags & TypeFlags.Never ? silentNeverType : narrowedType; + return createFlowType(resultType, incomplete); } function getTypeAtSwitchClause(flow: FlowSwitchClause): FlowType { @@ -8601,7 +8734,7 @@ namespace ts { seenIncomplete = true; } } - return createFlowType(getUnionType(antecedentTypes, subtypeReduction), seenIncomplete); + return createFlowType(getUnionOrEvolvingArrayType(antecedentTypes, subtypeReduction), seenIncomplete); } function getTypeAtFlowLoopLabel(flow: FlowLabel): FlowType { @@ -8621,7 +8754,7 @@ namespace ts { // junction is always the non-looping control flow path that leads to the top. for (let i = flowLoopStart; i < flowLoopCount; i++) { if (flowLoopNodes[i] === flow && flowLoopKeys[i] === key) { - return createFlowType(getUnionType(flowLoopTypes[i]), /*incomplete*/ true); + return createFlowType(getUnionOrEvolvingArrayType(flowLoopTypes[i], false), /*incomplete*/ true); } } // Add the flow loop junction and reference to the in-process stack and analyze @@ -8664,7 +8797,7 @@ namespace ts { } // The result is incomplete if the first antecedent (the non-looping control flow path) // is incomplete. - const result = getUnionType(antecedentTypes, subtypeReduction); + const result = getUnionOrEvolvingArrayType(antecedentTypes, subtypeReduction); if (isIncomplete(firstAntecedentType)) { return createFlowType(result, /*incomplete*/ true); } @@ -9143,19 +9276,19 @@ namespace ts { // the entire control flow graph from the variable's declaration (i.e. when the flow container and // declaration container are the same). const assumeInitialized = isParameter || isOuterVariable || - type !== autoType && (!strictNullChecks || (type.flags & TypeFlags.Any) !== 0) || + type !== autoType && type !== autoArrayType && (!strictNullChecks || (type.flags & TypeFlags.Any) !== 0) || isInAmbientContext(declaration); const flowType = getFlowTypeOfReference(node, type, assumeInitialized, 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. - if (type === autoType) { - if (flowType === autoType) { + if (type === autoType || type === autoArrayType) { + if (flowType === type) { if (compilerOptions.noImplicitAny) { - error(declaration.name, Diagnostics.Variable_0_implicitly_has_type_any_in_some_locations_where_its_type_cannot_be_determined, symbolToString(symbol)); - error(node, Diagnostics.Variable_0_implicitly_has_an_1_type, symbolToString(symbol), typeToString(anyType)); + error(declaration.name, Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined, symbolToString(symbol), typeToString(type)); + error(node, Diagnostics.Variable_0_implicitly_has_an_1_type, symbolToString(symbol), typeToString(type)); } - return anyType; + return convertAutoToAny(type); } } else if (!assumeInitialized && !(getFalsyFlags(type) & TypeFlags.Undefined) && getFalsyFlags(flowType) & TypeFlags.Undefined) { @@ -15904,7 +16037,7 @@ namespace ts { } function convertAutoToAny(type: Type) { - return type === autoType ? anyType : type; + return type === autoType ? anyType : type === autoArrayType ? anyArrayType : type; } // Check variable, parameter, or property declaration @@ -19307,6 +19440,7 @@ namespace ts { } anyArrayType = createArrayType(anyType); + autoArrayType = createArrayType(autoType); const symbol = getGlobalSymbol("ReadonlyArray", SymbolFlags.Type, /*diagnostic*/ undefined); globalReadonlyArrayType = symbol && getTypeOfGlobalSymbol(symbol, /*arity*/ 1); diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index fe196b29216da..7a73b31e1a8da 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -2953,7 +2953,7 @@ "category": "Error", "code": 7033 }, - "Variable '{0}' implicitly has type 'any' in some locations where its type cannot be determined.": { + "Variable '{0}' implicitly has type '{1}' in some locations where its type cannot be determined.": { "category": "Error", "code": 7034 }, diff --git a/src/compiler/types.ts b/src/compiler/types.ts index ab48ae543c2b7..5b34c5bfbf67e 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1681,8 +1681,9 @@ namespace ts { TrueCondition = 1 << 5, // Condition known to be true FalseCondition = 1 << 6, // Condition known to be false SwitchClause = 1 << 7, // Switch statement clause - Referenced = 1 << 8, // Referenced as antecedent once - Shared = 1 << 9, // Referenced as antecedent more than once + ArrayMutation = 1 << 8, // Potential array mutation + Referenced = 1 << 9, // Referenced as antecedent once + Shared = 1 << 10, // Referenced as antecedent more than once Label = BranchLabel | LoopLabel, Condition = TrueCondition | FalseCondition } @@ -1725,6 +1726,13 @@ namespace ts { antecedent: FlowNode; } + // FlowArrayMutation represents a node potentially mutates an array, i.e. an + // operation of the form 'x.push(value)' or 'x[n] = value'. + export interface FlowArrayMutation extends FlowNode { + node: Expression; + antecedent: FlowNode; + } + export type FlowType = Type | IncompleteType; // Incomplete types occur during control flow analysis of loops. An IncompleteType @@ -2521,6 +2529,8 @@ namespace ts { export interface AnonymousType extends ObjectType { target?: AnonymousType; // Instantiation target mapper?: TypeMapper; // Instantiation mapper + elementType?: Type; // Element expressions of evolving array type + finalArrayType?: Type; // Final array type of evolving array type } /* @internal */ From b5c10553b5ef29da07d66b5ca0a0e3689dc14790 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 6 Oct 2016 11:05:55 -0700 Subject: [PATCH 02/22] Accept new baselines --- .../reference/arrayLiterals2ES5.types | 8 ++--- .../genericTypeParameterEquivalence2.types | 2 +- .../reference/globalThisCapture.types | 4 +-- .../implicitAnyWidenToAny.errors.txt | 5 +-- .../reference/localClassesInLoop.types | 12 +++---- .../reference/localClassesInLoop_ES6.types | 12 +++---- .../strictNullChecksNoWidening.types | 2 +- tests/baselines/reference/typedArrays.types | 36 +++++++++---------- 8 files changed, 39 insertions(+), 42 deletions(-) diff --git a/tests/baselines/reference/arrayLiterals2ES5.types b/tests/baselines/reference/arrayLiterals2ES5.types index c3256db925e02..4c5f895114d5c 100644 --- a/tests/baselines/reference/arrayLiterals2ES5.types +++ b/tests/baselines/reference/arrayLiterals2ES5.types @@ -193,10 +193,10 @@ var d5 = [...temp3]; >temp3 : any[] var d6 = [...temp4]; ->d6 : any[] ->[...temp4] : any[] ->...temp4 : any ->temp4 : any[] +>d6 : never[] +>[...temp4] : never[] +>...temp4 : never +>temp4 : never[] var d7 = [...[...temp1]]; >d7 : number[] diff --git a/tests/baselines/reference/genericTypeParameterEquivalence2.types b/tests/baselines/reference/genericTypeParameterEquivalence2.types index 6ab297fe32cfd..095ce27c752ab 100644 --- a/tests/baselines/reference/genericTypeParameterEquivalence2.types +++ b/tests/baselines/reference/genericTypeParameterEquivalence2.types @@ -105,7 +105,7 @@ function filter(f: (a: A) => boolean, ar: A[]): A[] { } ); return ret; ->ret : any[] +>ret : never[] } // length :: [a] -> Num diff --git a/tests/baselines/reference/globalThisCapture.types b/tests/baselines/reference/globalThisCapture.types index 063100ff78eaf..92a20ff930eee 100644 --- a/tests/baselines/reference/globalThisCapture.types +++ b/tests/baselines/reference/globalThisCapture.types @@ -13,7 +13,7 @@ var parts = []; // Ensure that the generated code is correct parts[0]; ->parts[0] : any ->parts : any[] +>parts[0] : never +>parts : never[] >0 : 0 diff --git a/tests/baselines/reference/implicitAnyWidenToAny.errors.txt b/tests/baselines/reference/implicitAnyWidenToAny.errors.txt index 95739a450a888..7a9314d982f96 100644 --- a/tests/baselines/reference/implicitAnyWidenToAny.errors.txt +++ b/tests/baselines/reference/implicitAnyWidenToAny.errors.txt @@ -1,8 +1,7 @@ tests/cases/compiler/implicitAnyWidenToAny.ts(4,5): error TS7005: Variable 'widenArray' implicitly has an 'any[]' type. -tests/cases/compiler/implicitAnyWidenToAny.ts(5,5): error TS7005: Variable 'emptyArray' implicitly has an 'any[]' type. -==== tests/cases/compiler/implicitAnyWidenToAny.ts (2 errors) ==== +==== tests/cases/compiler/implicitAnyWidenToAny.ts (1 errors) ==== // these should be errors var x = null; // error at "x" var x1 = undefined; // error at "x1" @@ -10,8 +9,6 @@ tests/cases/compiler/implicitAnyWidenToAny.ts(5,5): error TS7005: Variable 'empt ~~~~~~~~~~ !!! error TS7005: Variable 'widenArray' implicitly has an 'any[]' type. var emptyArray = []; // error at "emptyArray" - ~~~~~~~~~~ -!!! error TS7005: Variable 'emptyArray' implicitly has an 'any[]' type. // these should not be error class AnimalObj { diff --git a/tests/baselines/reference/localClassesInLoop.types b/tests/baselines/reference/localClassesInLoop.types index c88bfc0435d0c..8805044589807 100644 --- a/tests/baselines/reference/localClassesInLoop.types +++ b/tests/baselines/reference/localClassesInLoop.types @@ -35,12 +35,12 @@ use(data[0]() === data[1]()); >use(data[0]() === data[1]()) : any >use : (a: any) => any >data[0]() === data[1]() : boolean ->data[0]() : any ->data[0] : any ->data : any[] +>data[0]() : typeof C +>data[0] : () => typeof C +>data : (() => typeof C)[] >0 : 0 ->data[1]() : any ->data[1] : any ->data : any[] +>data[1]() : typeof C +>data[1] : () => typeof C +>data : (() => typeof C)[] >1 : 1 diff --git a/tests/baselines/reference/localClassesInLoop_ES6.types b/tests/baselines/reference/localClassesInLoop_ES6.types index 9d487f6c60051..390f4b8b1ddb2 100644 --- a/tests/baselines/reference/localClassesInLoop_ES6.types +++ b/tests/baselines/reference/localClassesInLoop_ES6.types @@ -36,12 +36,12 @@ use(data[0]() === data[1]()); >use(data[0]() === data[1]()) : any >use : (a: any) => any >data[0]() === data[1]() : boolean ->data[0]() : any ->data[0] : any ->data : any[] +>data[0]() : typeof C +>data[0] : () => typeof C +>data : (() => typeof C)[] >0 : 0 ->data[1]() : any ->data[1] : any ->data : any[] +>data[1]() : typeof C +>data[1] : () => typeof C +>data : (() => typeof C)[] >1 : 1 diff --git a/tests/baselines/reference/strictNullChecksNoWidening.types b/tests/baselines/reference/strictNullChecksNoWidening.types index 375977ae37514..e24797850ee2b 100644 --- a/tests/baselines/reference/strictNullChecksNoWidening.types +++ b/tests/baselines/reference/strictNullChecksNoWidening.types @@ -14,7 +14,7 @@ var a3 = void 0; >0 : 0 var b1 = []; ->b1 : never[] +>b1 : any[] >[] : never[] var b2 = [,]; diff --git a/tests/baselines/reference/typedArrays.types b/tests/baselines/reference/typedArrays.types index 444d6a74e4303..cd2103f646122 100644 --- a/tests/baselines/reference/typedArrays.types +++ b/tests/baselines/reference/typedArrays.types @@ -1,7 +1,7 @@ === tests/cases/compiler/typedArrays.ts === function CreateTypedArrayTypes() { ->CreateTypedArrayTypes : () => any[] +>CreateTypedArrayTypes : () => (Int8ArrayConstructor | Uint8ArrayConstructor | Int16ArrayConstructor | Uint16ArrayConstructor | Int32ArrayConstructor | Uint32ArrayConstructor | Float32ArrayConstructor | Float64ArrayConstructor | Uint8ClampedArrayConstructor)[] var typedArrays = []; >typedArrays : any[] @@ -71,11 +71,11 @@ function CreateTypedArrayTypes() { >Uint8ClampedArray : Uint8ClampedArrayConstructor return typedArrays; ->typedArrays : any[] +>typedArrays : (Int8ArrayConstructor | Uint8ArrayConstructor | Int16ArrayConstructor | Uint16ArrayConstructor | Int32ArrayConstructor | Uint32ArrayConstructor | Float32ArrayConstructor | Float64ArrayConstructor | Uint8ClampedArrayConstructor)[] } function CreateTypedArrayInstancesFromLength(obj: number) { ->CreateTypedArrayInstancesFromLength : (obj: number) => any[] +>CreateTypedArrayInstancesFromLength : (obj: number) => (Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array)[] >obj : number var typedArrays = []; @@ -164,11 +164,11 @@ function CreateTypedArrayInstancesFromLength(obj: number) { >obj : number return typedArrays; ->typedArrays : any[] +>typedArrays : (Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array)[] } function CreateTypedArrayInstancesFromArray(obj: number[]) { ->CreateTypedArrayInstancesFromArray : (obj: number[]) => any[] +>CreateTypedArrayInstancesFromArray : (obj: number[]) => (Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array)[] >obj : number[] var typedArrays = []; @@ -257,11 +257,11 @@ function CreateTypedArrayInstancesFromArray(obj: number[]) { >obj : number[] return typedArrays; ->typedArrays : any[] +>typedArrays : (Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array)[] } function CreateIntegerTypedArraysFromArray2(obj:number[]) { ->CreateIntegerTypedArraysFromArray2 : (obj: number[]) => any[] +>CreateIntegerTypedArraysFromArray2 : (obj: number[]) => (Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array)[] >obj : number[] var typedArrays = []; @@ -368,11 +368,11 @@ function CreateIntegerTypedArraysFromArray2(obj:number[]) { >obj : number[] return typedArrays; ->typedArrays : any[] +>typedArrays : (Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array)[] } function CreateIntegerTypedArraysFromArrayLike(obj:ArrayLike) { ->CreateIntegerTypedArraysFromArrayLike : (obj: ArrayLike) => any[] +>CreateIntegerTypedArraysFromArrayLike : (obj: ArrayLike) => (Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array)[] >obj : ArrayLike >ArrayLike : ArrayLike @@ -480,11 +480,11 @@ function CreateIntegerTypedArraysFromArrayLike(obj:ArrayLike) { >obj : ArrayLike return typedArrays; ->typedArrays : any[] +>typedArrays : (Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array)[] } function CreateTypedArraysOf(obj) { ->CreateTypedArraysOf : (obj: any) => any[] +>CreateTypedArraysOf : (obj: any) => (Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array)[] >obj : any var typedArrays = []; @@ -600,11 +600,11 @@ function CreateTypedArraysOf(obj) { >obj : any return typedArrays; ->typedArrays : any[] +>typedArrays : (Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array)[] } function CreateTypedArraysOf2() { ->CreateTypedArraysOf2 : () => any[] +>CreateTypedArraysOf2 : () => (Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array)[] var typedArrays = []; >typedArrays : any[] @@ -737,11 +737,11 @@ function CreateTypedArraysOf2() { >4 : 4 return typedArrays; ->typedArrays : any[] +>typedArrays : (Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array)[] } function CreateTypedArraysFromMapFn(obj:ArrayLike, mapFn: (n:number, v:number)=> number) { ->CreateTypedArraysFromMapFn : (obj: ArrayLike, mapFn: (n: number, v: number) => number) => any[] +>CreateTypedArraysFromMapFn : (obj: ArrayLike, mapFn: (n: number, v: number) => number) => (Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array)[] >obj : ArrayLike >ArrayLike : ArrayLike >mapFn : (n: number, v: number) => number @@ -861,11 +861,11 @@ function CreateTypedArraysFromMapFn(obj:ArrayLike, mapFn: (n:number, v:n >mapFn : (n: number, v: number) => number return typedArrays; ->typedArrays : any[] +>typedArrays : (Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array)[] } function CreateTypedArraysFromThisObj(obj:ArrayLike, mapFn: (n:number, v:number)=> number, thisArg: {}) { ->CreateTypedArraysFromThisObj : (obj: ArrayLike, mapFn: (n: number, v: number) => number, thisArg: {}) => any[] +>CreateTypedArraysFromThisObj : (obj: ArrayLike, mapFn: (n: number, v: number) => number, thisArg: {}) => (Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array)[] >obj : ArrayLike >ArrayLike : ArrayLike >mapFn : (n: number, v: number) => number @@ -995,5 +995,5 @@ function CreateTypedArraysFromThisObj(obj:ArrayLike, mapFn: (n:number, v >thisArg : {} return typedArrays; ->typedArrays : any[] +>typedArrays : (Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array)[] } From 9c4190b1fb31b6317efc15430c2047ca78779ff0 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 6 Oct 2016 13:25:42 -0700 Subject: [PATCH 03/22] Introduce sameMap function --- src/compiler/checker.ts | 26 ++++++++++++++++---------- src/compiler/core.ts | 24 ++++++++++++++++++++++-- 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index eff5296754475..414a92ffed408 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7404,7 +7404,7 @@ namespace ts { type.flags & TypeFlags.NumberLiteral ? numberType : type.flags & TypeFlags.BooleanLiteral ? booleanType : type.flags & TypeFlags.EnumLiteral ? (type).baseType : - type.flags & TypeFlags.Union && !(type.flags & TypeFlags.Enum) ? getUnionType(map((type).types, getBaseTypeOfLiteralType)) : + type.flags & TypeFlags.Union && !(type.flags & TypeFlags.Enum) ? getUnionType(sameMap((type).types, getBaseTypeOfLiteralType)) : type; } @@ -7413,7 +7413,7 @@ namespace ts { type.flags & TypeFlags.NumberLiteral && type.flags & TypeFlags.FreshLiteral ? numberType : type.flags & TypeFlags.BooleanLiteral ? booleanType : type.flags & TypeFlags.EnumLiteral ? (type).baseType : - type.flags & TypeFlags.Union && !(type.flags & TypeFlags.Enum) ? getUnionType(map((type).types, getWidenedLiteralType)) : + type.flags & TypeFlags.Union && !(type.flags & TypeFlags.Enum) ? getUnionType(sameMap((type).types, getWidenedLiteralType)) : type; } @@ -7552,10 +7552,10 @@ namespace ts { return getWidenedTypeOfObjectLiteral(type); } if (type.flags & TypeFlags.Union) { - return getUnionType(map((type).types, getWidenedConstituentType)); + return getUnionType(sameMap((type).types, getWidenedConstituentType)); } if (isArrayType(type) || isTupleType(type)) { - return createTypeReference((type).target, map((type).typeArguments, getWidenedType)); + return createTypeReference((type).target, sameMap((type).typeArguments, getWidenedType)); } } return type; @@ -7973,7 +7973,7 @@ namespace ts { const widenLiteralTypes = context.inferences[index].topLevel && !hasPrimitiveConstraint(signature.typeParameters[index]) && (context.inferences[index].isFixed || !isTypeParameterAtTopLevel(getReturnTypeOfSignature(signature), signature.typeParameters[index])); - const baseInferences = widenLiteralTypes ? map(inferences, getWidenedLiteralType) : inferences; + const baseInferences = widenLiteralTypes ? sameMap(inferences, getWidenedLiteralType) : inferences; // Infer widened union or supertype, or the unknown type for no common supertype const unionOrSuperType = context.inferUnionTypes ? getUnionType(baseInferences, /*subtypeReduction*/ true) : getCommonSupertype(baseInferences); inferredType = unionOrSuperType ? getWidenedType(unionOrSuperType) : unknownType; @@ -8485,9 +8485,15 @@ namespace ts { return type.flags & TypeFlags.Anonymous && !!(type).elementType; } + function createFinalArrayType(elementType: Type) { + return createArrayType(elementType !== neverType ? + getUnionType([elementType], /*subtypeReduction*/ true) : + strictNullChecks ? neverType : undefinedWideningType); + } + // We perform subtype reduction upon obtaining the final array type from an evolving array type. function getFinalArrayType(evolvingArrayType: AnonymousType): Type { - return evolvingArrayType.finalArrayType || (evolvingArrayType.finalArrayType = createArrayType(getUnionType([evolvingArrayType.elementType], /*subtypeReduction*/ true))); + return evolvingArrayType.finalArrayType || (evolvingArrayType.finalArrayType = createFinalArrayType(evolvingArrayType.elementType)); } function finalizeEvolvingArrayType(type: Type): Type { @@ -8504,7 +8510,7 @@ namespace ts { function getUnionOrEvolvingArrayType(types: Type[], subtypeReduction: boolean) { return types.length && every(types, isEvolvingArrayType) ? getEvolvingArrayType(getUnionType(map(types, getElementTypeOfEvolvingArrayType))) : - getUnionType(map(types, finalizeEvolvingArrayType), subtypeReduction); + getUnionType(sameMap(types, finalizeEvolvingArrayType), subtypeReduction); } // Return true if the given node is 'x' in an 'x.push(value)' operation. @@ -8754,7 +8760,7 @@ namespace ts { // junction is always the non-looping control flow path that leads to the top. for (let i = flowLoopStart; i < flowLoopCount; i++) { if (flowLoopNodes[i] === flow && flowLoopKeys[i] === key) { - return createFlowType(getUnionOrEvolvingArrayType(flowLoopTypes[i], false), /*incomplete*/ true); + return createFlowType(getUnionOrEvolvingArrayType(flowLoopTypes[i], /*subtypeReduction*/ false), /*incomplete*/ true); } } // Add the flow loop junction and reference to the in-process stack and analyze @@ -10728,7 +10734,7 @@ namespace ts { } } - return getUnionType(signatures.map(getReturnTypeOfSignature), /*subtypeReduction*/ true); + return getUnionType(map(signatures, getReturnTypeOfSignature), /*subtypeReduction*/ true); } /// e.g. "props" for React.d.ts, @@ -10778,7 +10784,7 @@ namespace ts { } if (elemType.flags & TypeFlags.Union) { const types = (elemType).types; - return getUnionType(types.map(type => { + return getUnionType(map(types, type => { return getResolvedJsxType(node, type, elemClassType); }), /*subtypeReduction*/ true); } diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 72d05e1cfd7c5..bb3aaf232595f 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -257,13 +257,33 @@ namespace ts { if (array) { result = []; for (let i = 0; i < array.length; i++) { - const v = array[i]; - result.push(f(v, i)); + result.push(f(array[i], i)); } } return result; } + // Maps from T to T and avoids allocation of all elements map to themselves + export function sameMap(array: T[], f: (x: T, i: number) => T): T[] { + let result: T[]; + if (array) { + for (let i = 0; i < array.length; i++) { + if (result) { + result.push(f(array[i], i)); + } + else { + const item = array[i]; + const mapped = f(item, i); + if (item !== mapped) { + result = array.slice(0, i); + result.push(mapped); + } + } + } + } + return result || array; + } + /** * Flattens an array containing a mix of array or non-array elements. * From 144f4ffc7c836834947ecb6541225a47aa8e1af8 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 6 Oct 2016 13:33:13 -0700 Subject: [PATCH 04/22] Accept new baselines --- tests/baselines/reference/arrayLiterals2ES5.types | 8 ++++---- .../reference/genericTypeParameterEquivalence2.types | 2 +- tests/baselines/reference/globalThisCapture.types | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/baselines/reference/arrayLiterals2ES5.types b/tests/baselines/reference/arrayLiterals2ES5.types index 4c5f895114d5c..833966a6e484e 100644 --- a/tests/baselines/reference/arrayLiterals2ES5.types +++ b/tests/baselines/reference/arrayLiterals2ES5.types @@ -193,10 +193,10 @@ var d5 = [...temp3]; >temp3 : any[] var d6 = [...temp4]; ->d6 : never[] ->[...temp4] : never[] ->...temp4 : never ->temp4 : never[] +>d6 : any[] +>[...temp4] : undefined[] +>...temp4 : undefined +>temp4 : undefined[] var d7 = [...[...temp1]]; >d7 : number[] diff --git a/tests/baselines/reference/genericTypeParameterEquivalence2.types b/tests/baselines/reference/genericTypeParameterEquivalence2.types index 095ce27c752ab..512537645781d 100644 --- a/tests/baselines/reference/genericTypeParameterEquivalence2.types +++ b/tests/baselines/reference/genericTypeParameterEquivalence2.types @@ -105,7 +105,7 @@ function filter(f: (a: A) => boolean, ar: A[]): A[] { } ); return ret; ->ret : never[] +>ret : undefined[] } // length :: [a] -> Num diff --git a/tests/baselines/reference/globalThisCapture.types b/tests/baselines/reference/globalThisCapture.types index 92a20ff930eee..d17c899d1ed88 100644 --- a/tests/baselines/reference/globalThisCapture.types +++ b/tests/baselines/reference/globalThisCapture.types @@ -13,7 +13,7 @@ var parts = []; // Ensure that the generated code is correct parts[0]; ->parts[0] : never ->parts : never[] +>parts[0] : undefined +>parts : undefined[] >0 : 0 From a9c2b23df52a2abaffae4ba1470ee87e05aae731 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 6 Oct 2016 13:33:28 -0700 Subject: [PATCH 05/22] Add tests --- .../cases/compiler/controlFlowArrayErrors.ts | 59 ++++++++ tests/cases/compiler/controlFlowArrays.ts | 141 ++++++++++++++++++ 2 files changed, 200 insertions(+) create mode 100644 tests/cases/compiler/controlFlowArrayErrors.ts create mode 100644 tests/cases/compiler/controlFlowArrays.ts diff --git a/tests/cases/compiler/controlFlowArrayErrors.ts b/tests/cases/compiler/controlFlowArrayErrors.ts new file mode 100644 index 0000000000000..84da65c41dc5b --- /dev/null +++ b/tests/cases/compiler/controlFlowArrayErrors.ts @@ -0,0 +1,59 @@ +// @noImplicitAny: true + +declare function cond(): boolean; + +function f1() { + let x = []; + let y = x; // Implicit any[] error + x.push(5); + let z = x; +} + +function f2() { + let x; + x = []; + let y = x; // Implicit any[] error + x.push(5); + let z = x; +} + +function f3() { + let x = []; // Implicit any[] error in some locations + x.push(5); + function g() { + x; // Implicit any[] error + } +} + +function f4() { + let x; + x = [5, "hello"]; // Non-evolving array + x.push(true); // Error +} + +function f5() { + let x = [5, "hello"]; // Non-evolving array + x.push(true); // Error +} + +function f6() { + let x; + if (cond()) { + x = []; + x.push(5); + x.push("hello"); + } + else { + x = [true]; // Non-evolving array + } + x; // boolean[] | (string | number)[] + x.push(99); // Error +} + +function f7() { + let x = []; // x has evolving array value + x.push(5); + let y = x; // y has non-evolving array value + x.push("hello"); // Ok + y.push("hello"); // Error +} \ No newline at end of file diff --git a/tests/cases/compiler/controlFlowArrays.ts b/tests/cases/compiler/controlFlowArrays.ts new file mode 100644 index 0000000000000..d5eaa9c4320b6 --- /dev/null +++ b/tests/cases/compiler/controlFlowArrays.ts @@ -0,0 +1,141 @@ +// @strictNullChecks: true +// @noImplicitAny: true + +declare function cond(): boolean; + +function f1() { + let x = []; + x[0] = 5; + x[1] = "hello"; + x[2] = true; + return x; // (string | number | boolean)[] +} + +function f2() { + let x = []; + x.push(5); + x.push("hello"); + x.push(true); + return x; // (string | number | boolean)[] +} + +function f3() { + let x; + x = []; + x.push(5, "hello"); + return x; // (string | number)[] +} + +function f4() { + let x = []; + if (cond()) { + x.push(5); + } + else { + x.push("hello"); + } + return x; // (string | number)[] +} + +function f5() { + let x; + if (cond()) { + x = []; + x.push(5); + } + else { + x = []; + x.push("hello"); + } + return x; // (string | number)[] +} + +function f6() { + let x; + if (cond()) { + x = 5; + } + else { + x = []; + x.push("hello"); + } + return x; // number | string[] +} + +function f7() { + let x = null; + if (cond()) { + x = []; + while (cond()) { + x.push("hello"); + } + } + return x; // string[] | null +} + +function f8() { + let x = []; + x.push(5); + if (cond()) return x; // number[] + x.push("hello"); + if (cond()) return x; // (string | number)[] + x.push(true); + return x; // (string | number | boolean)[] +} + +function f9() { + let x = []; + if (cond()) { + x.push(5); + return x; // number[] + } + else { + x.push("hello"); + return x; // string[] + } +} + +function f10() { + let x = []; + if (cond()) { + x.push(true); + x; // boolean[] + } + else { + x.push(5); + x; // number[] + while (cond()) { + x.push("hello"); + } + x; // (string | number)[] + } + x.push(99); + return x; // (string | number | boolean)[] +} + +function f11() { + let x = []; + return x; // never[] +} + +function f12() { + let x; + x = []; + return x; // never[] +} + +function f13() { + var x = []; + x.push(5); + x.push("hello"); + x.push(true); + return x; // (string | number | boolean)[] +} + +function f14() { + const x = []; + x.push(5); + x.push("hello"); + x.push(true); + return x; // (string | number | boolean)[] +} \ No newline at end of file From c0c2271d31a361528deebe88e91539c2635ccbf9 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 6 Oct 2016 13:35:02 -0700 Subject: [PATCH 06/22] Accept new baselines --- .../controlFlowArrayErrors.errors.txt | 85 ++++ .../reference/controlFlowArrayErrors.js | 110 +++++ .../baselines/reference/controlFlowArrays.js | 267 +++++++++++ .../reference/controlFlowArrays.symbols | 350 ++++++++++++++ .../reference/controlFlowArrays.types | 447 ++++++++++++++++++ 5 files changed, 1259 insertions(+) create mode 100644 tests/baselines/reference/controlFlowArrayErrors.errors.txt create mode 100644 tests/baselines/reference/controlFlowArrayErrors.js create mode 100644 tests/baselines/reference/controlFlowArrays.js create mode 100644 tests/baselines/reference/controlFlowArrays.symbols create mode 100644 tests/baselines/reference/controlFlowArrays.types diff --git a/tests/baselines/reference/controlFlowArrayErrors.errors.txt b/tests/baselines/reference/controlFlowArrayErrors.errors.txt new file mode 100644 index 0000000000000..7475c27324349 --- /dev/null +++ b/tests/baselines/reference/controlFlowArrayErrors.errors.txt @@ -0,0 +1,85 @@ +tests/cases/compiler/controlFlowArrayErrors.ts(6,9): error TS7005: Variable 'y' implicitly has an 'any[]' type. +tests/cases/compiler/controlFlowArrayErrors.ts(14,9): error TS7005: Variable 'y' implicitly has an 'any[]' type. +tests/cases/compiler/controlFlowArrayErrors.ts(20,9): error TS7034: Variable 'x' implicitly has type 'any[]' in some locations where its type cannot be determined. +tests/cases/compiler/controlFlowArrayErrors.ts(23,9): error TS7005: Variable 'x' implicitly has an 'any[]' type. +tests/cases/compiler/controlFlowArrayErrors.ts(30,12): error TS2345: Argument of type 'true' is not assignable to parameter of type 'string | number'. +tests/cases/compiler/controlFlowArrayErrors.ts(35,12): error TS2345: Argument of type 'true' is not assignable to parameter of type 'string | number'. +tests/cases/compiler/controlFlowArrayErrors.ts(49,5): error TS2349: Cannot invoke an expression whose type lacks a call signature. Type '((...items: (string | number)[]) => number) | ((...items: boolean[]) => number)' has no compatible call signatures. +tests/cases/compiler/controlFlowArrayErrors.ts(57,12): error TS2345: Argument of type '"hello"' is not assignable to parameter of type 'number'. + + +==== tests/cases/compiler/controlFlowArrayErrors.ts (8 errors) ==== + + declare function cond(): boolean; + + function f1() { + let x = []; + let y = x; // Implicit any[] error + ~ +!!! error TS7005: Variable 'y' implicitly has an 'any[]' type. + x.push(5); + let z = x; + } + + function f2() { + let x; + x = []; + let y = x; // Implicit any[] error + ~ +!!! error TS7005: Variable 'y' implicitly has an 'any[]' type. + x.push(5); + let z = x; + } + + function f3() { + let x = []; // Implicit any[] error in some locations + ~ +!!! error TS7034: Variable 'x' implicitly has type 'any[]' in some locations where its type cannot be determined. + x.push(5); + function g() { + x; // Implicit any[] error + ~ +!!! error TS7005: Variable 'x' implicitly has an 'any[]' type. + } + } + + function f4() { + let x; + x = [5, "hello"]; // Non-evolving array + x.push(true); // Error + ~~~~ +!!! error TS2345: Argument of type 'true' is not assignable to parameter of type 'string | number'. + } + + function f5() { + let x = [5, "hello"]; // Non-evolving array + x.push(true); // Error + ~~~~ +!!! error TS2345: Argument of type 'true' is not assignable to parameter of type 'string | number'. + } + + function f6() { + let x; + if (cond()) { + x = []; + x.push(5); + x.push("hello"); + } + else { + x = [true]; // Non-evolving array + } + x; // boolean[] | (string | number)[] + x.push(99); // Error + ~~~~~~~~~~ +!!! error TS2349: Cannot invoke an expression whose type lacks a call signature. Type '((...items: (string | number)[]) => number) | ((...items: boolean[]) => number)' has no compatible call signatures. + } + + function f7() { + let x = []; // x has evolving array value + x.push(5); + let y = x; // y has non-evolving array value + x.push("hello"); // Ok + y.push("hello"); // Error + ~~~~~~~ +!!! error TS2345: Argument of type '"hello"' is not assignable to parameter of type 'number'. + } \ No newline at end of file diff --git a/tests/baselines/reference/controlFlowArrayErrors.js b/tests/baselines/reference/controlFlowArrayErrors.js new file mode 100644 index 0000000000000..c1589449334c2 --- /dev/null +++ b/tests/baselines/reference/controlFlowArrayErrors.js @@ -0,0 +1,110 @@ +//// [controlFlowArrayErrors.ts] + +declare function cond(): boolean; + +function f1() { + let x = []; + let y = x; // Implicit any[] error + x.push(5); + let z = x; +} + +function f2() { + let x; + x = []; + let y = x; // Implicit any[] error + x.push(5); + let z = x; +} + +function f3() { + let x = []; // Implicit any[] error in some locations + x.push(5); + function g() { + x; // Implicit any[] error + } +} + +function f4() { + let x; + x = [5, "hello"]; // Non-evolving array + x.push(true); // Error +} + +function f5() { + let x = [5, "hello"]; // Non-evolving array + x.push(true); // Error +} + +function f6() { + let x; + if (cond()) { + x = []; + x.push(5); + x.push("hello"); + } + else { + x = [true]; // Non-evolving array + } + x; // boolean[] | (string | number)[] + x.push(99); // Error +} + +function f7() { + let x = []; // x has evolving array value + x.push(5); + let y = x; // y has non-evolving array value + x.push("hello"); // Ok + y.push("hello"); // Error +} + +//// [controlFlowArrayErrors.js] +function f1() { + var x = []; + var y = x; // Implicit any[] error + x.push(5); + var z = x; +} +function f2() { + var x; + x = []; + var y = x; // Implicit any[] error + x.push(5); + var z = x; +} +function f3() { + var x = []; // Implicit any[] error in some locations + x.push(5); + function g() { + x; // Implicit any[] error + } +} +function f4() { + var x; + x = [5, "hello"]; // Non-evolving array + x.push(true); // Error +} +function f5() { + var x = [5, "hello"]; // Non-evolving array + x.push(true); // Error +} +function f6() { + var x; + if (cond()) { + x = []; + x.push(5); + x.push("hello"); + } + else { + x = [true]; // Non-evolving array + } + x; // boolean[] | (string | number)[] + x.push(99); // Error +} +function f7() { + var x = []; // x has evolving array value + x.push(5); + var y = x; // y has non-evolving array value + x.push("hello"); // Ok + y.push("hello"); // Error +} diff --git a/tests/baselines/reference/controlFlowArrays.js b/tests/baselines/reference/controlFlowArrays.js new file mode 100644 index 0000000000000..26885aecd8bd9 --- /dev/null +++ b/tests/baselines/reference/controlFlowArrays.js @@ -0,0 +1,267 @@ +//// [controlFlowArrays.ts] + +declare function cond(): boolean; + +function f1() { + let x = []; + x[0] = 5; + x[1] = "hello"; + x[2] = true; + return x; // (string | number | boolean)[] +} + +function f2() { + let x = []; + x.push(5); + x.push("hello"); + x.push(true); + return x; // (string | number | boolean)[] +} + +function f3() { + let x; + x = []; + x.push(5, "hello"); + return x; // (string | number)[] +} + +function f4() { + let x = []; + if (cond()) { + x.push(5); + } + else { + x.push("hello"); + } + return x; // (string | number)[] +} + +function f5() { + let x; + if (cond()) { + x = []; + x.push(5); + } + else { + x = []; + x.push("hello"); + } + return x; // (string | number)[] +} + +function f6() { + let x; + if (cond()) { + x = 5; + } + else { + x = []; + x.push("hello"); + } + return x; // number | string[] +} + +function f7() { + let x = null; + if (cond()) { + x = []; + while (cond()) { + x.push("hello"); + } + } + return x; // string[] | null +} + +function f8() { + let x = []; + x.push(5); + if (cond()) return x; // number[] + x.push("hello"); + if (cond()) return x; // (string | number)[] + x.push(true); + return x; // (string | number | boolean)[] +} + +function f9() { + let x = []; + if (cond()) { + x.push(5); + return x; // number[] + } + else { + x.push("hello"); + return x; // string[] + } +} + +function f10() { + let x = []; + if (cond()) { + x.push(true); + x; // boolean[] + } + else { + x.push(5); + x; // number[] + while (cond()) { + x.push("hello"); + } + x; // (string | number)[] + } + x.push(99); + return x; // (string | number | boolean)[] +} + +function f11() { + let x = []; + return x; // never[] +} + +function f12() { + let x; + x = []; + return x; // never[] +} + +function f13() { + var x = []; + x.push(5); + x.push("hello"); + x.push(true); + return x; // (string | number | boolean)[] +} + +function f14() { + const x = []; + x.push(5); + x.push("hello"); + x.push(true); + return x; // (string | number | boolean)[] +} + +//// [controlFlowArrays.js] +function f1() { + var x = []; + x[0] = 5; + x[1] = "hello"; + x[2] = true; + return x; // (string | number | boolean)[] +} +function f2() { + var x = []; + x.push(5); + x.push("hello"); + x.push(true); + return x; // (string | number | boolean)[] +} +function f3() { + var x; + x = []; + x.push(5, "hello"); + return x; // (string | number)[] +} +function f4() { + var x = []; + if (cond()) { + x.push(5); + } + else { + x.push("hello"); + } + return x; // (string | number)[] +} +function f5() { + var x; + if (cond()) { + x = []; + x.push(5); + } + else { + x = []; + x.push("hello"); + } + return x; // (string | number)[] +} +function f6() { + var x; + if (cond()) { + x = 5; + } + else { + x = []; + x.push("hello"); + } + return x; // number | string[] +} +function f7() { + var x = null; + if (cond()) { + x = []; + while (cond()) { + x.push("hello"); + } + } + return x; // string[] | null +} +function f8() { + var x = []; + x.push(5); + if (cond()) + return x; // number[] + x.push("hello"); + if (cond()) + return x; // (string | number)[] + x.push(true); + return x; // (string | number | boolean)[] +} +function f9() { + var x = []; + if (cond()) { + x.push(5); + return x; // number[] + } + else { + x.push("hello"); + return x; // string[] + } +} +function f10() { + var x = []; + if (cond()) { + x.push(true); + x; // boolean[] + } + else { + x.push(5); + x; // number[] + while (cond()) { + x.push("hello"); + } + x; // (string | number)[] + } + x.push(99); + return x; // (string | number | boolean)[] +} +function f11() { + var x = []; + return x; // never[] +} +function f12() { + var x; + x = []; + return x; // never[] +} +function f13() { + var x = []; + x.push(5); + x.push("hello"); + x.push(true); + return x; // (string | number | boolean)[] +} +function f14() { + var x = []; + x.push(5); + x.push("hello"); + x.push(true); + return x; // (string | number | boolean)[] +} diff --git a/tests/baselines/reference/controlFlowArrays.symbols b/tests/baselines/reference/controlFlowArrays.symbols new file mode 100644 index 0000000000000..5b5ef7f850617 --- /dev/null +++ b/tests/baselines/reference/controlFlowArrays.symbols @@ -0,0 +1,350 @@ +=== tests/cases/compiler/controlFlowArrays.ts === + +declare function cond(): boolean; +>cond : Symbol(cond, Decl(controlFlowArrays.ts, 0, 0)) + +function f1() { +>f1 : Symbol(f1, Decl(controlFlowArrays.ts, 1, 33)) + + let x = []; +>x : Symbol(x, Decl(controlFlowArrays.ts, 4, 7)) + + x[0] = 5; +>x : Symbol(x, Decl(controlFlowArrays.ts, 4, 7)) + + x[1] = "hello"; +>x : Symbol(x, Decl(controlFlowArrays.ts, 4, 7)) + + x[2] = true; +>x : Symbol(x, Decl(controlFlowArrays.ts, 4, 7)) + + return x; // (string | number | boolean)[] +>x : Symbol(x, Decl(controlFlowArrays.ts, 4, 7)) +} + +function f2() { +>f2 : Symbol(f2, Decl(controlFlowArrays.ts, 9, 1)) + + let x = []; +>x : Symbol(x, Decl(controlFlowArrays.ts, 12, 7)) + + x.push(5); +>x.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 12, 7)) +>push : Symbol(Array.push, Decl(lib.d.ts, --, --)) + + x.push("hello"); +>x.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 12, 7)) +>push : Symbol(Array.push, Decl(lib.d.ts, --, --)) + + x.push(true); +>x.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 12, 7)) +>push : Symbol(Array.push, Decl(lib.d.ts, --, --)) + + return x; // (string | number | boolean)[] +>x : Symbol(x, Decl(controlFlowArrays.ts, 12, 7)) +} + +function f3() { +>f3 : Symbol(f3, Decl(controlFlowArrays.ts, 17, 1)) + + let x; +>x : Symbol(x, Decl(controlFlowArrays.ts, 20, 7)) + + x = []; +>x : Symbol(x, Decl(controlFlowArrays.ts, 20, 7)) + + x.push(5, "hello"); +>x.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 20, 7)) +>push : Symbol(Array.push, Decl(lib.d.ts, --, --)) + + return x; // (string | number)[] +>x : Symbol(x, Decl(controlFlowArrays.ts, 20, 7)) +} + +function f4() { +>f4 : Symbol(f4, Decl(controlFlowArrays.ts, 24, 1)) + + let x = []; +>x : Symbol(x, Decl(controlFlowArrays.ts, 27, 7)) + + if (cond()) { +>cond : Symbol(cond, Decl(controlFlowArrays.ts, 0, 0)) + + x.push(5); +>x.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 27, 7)) +>push : Symbol(Array.push, Decl(lib.d.ts, --, --)) + } + else { + x.push("hello"); +>x.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 27, 7)) +>push : Symbol(Array.push, Decl(lib.d.ts, --, --)) + } + return x; // (string | number)[] +>x : Symbol(x, Decl(controlFlowArrays.ts, 27, 7)) +} + +function f5() { +>f5 : Symbol(f5, Decl(controlFlowArrays.ts, 35, 1)) + + let x; +>x : Symbol(x, Decl(controlFlowArrays.ts, 38, 7)) + + if (cond()) { +>cond : Symbol(cond, Decl(controlFlowArrays.ts, 0, 0)) + + x = []; +>x : Symbol(x, Decl(controlFlowArrays.ts, 38, 7)) + + x.push(5); +>x.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 38, 7)) +>push : Symbol(Array.push, Decl(lib.d.ts, --, --)) + } + else { + x = []; +>x : Symbol(x, Decl(controlFlowArrays.ts, 38, 7)) + + x.push("hello"); +>x.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 38, 7)) +>push : Symbol(Array.push, Decl(lib.d.ts, --, --)) + } + return x; // (string | number)[] +>x : Symbol(x, Decl(controlFlowArrays.ts, 38, 7)) +} + +function f6() { +>f6 : Symbol(f6, Decl(controlFlowArrays.ts, 48, 1)) + + let x; +>x : Symbol(x, Decl(controlFlowArrays.ts, 51, 7)) + + if (cond()) { +>cond : Symbol(cond, Decl(controlFlowArrays.ts, 0, 0)) + + x = 5; +>x : Symbol(x, Decl(controlFlowArrays.ts, 51, 7)) + } + else { + x = []; +>x : Symbol(x, Decl(controlFlowArrays.ts, 51, 7)) + + x.push("hello"); +>x.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 51, 7)) +>push : Symbol(Array.push, Decl(lib.d.ts, --, --)) + } + return x; // number | string[] +>x : Symbol(x, Decl(controlFlowArrays.ts, 51, 7)) +} + +function f7() { +>f7 : Symbol(f7, Decl(controlFlowArrays.ts, 60, 1)) + + let x = null; +>x : Symbol(x, Decl(controlFlowArrays.ts, 63, 7)) + + if (cond()) { +>cond : Symbol(cond, Decl(controlFlowArrays.ts, 0, 0)) + + x = []; +>x : Symbol(x, Decl(controlFlowArrays.ts, 63, 7)) + + while (cond()) { +>cond : Symbol(cond, Decl(controlFlowArrays.ts, 0, 0)) + + x.push("hello"); +>x.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 63, 7)) +>push : Symbol(Array.push, Decl(lib.d.ts, --, --)) + } + } + return x; // string[] | null +>x : Symbol(x, Decl(controlFlowArrays.ts, 63, 7)) +} + +function f8() { +>f8 : Symbol(f8, Decl(controlFlowArrays.ts, 71, 1)) + + let x = []; +>x : Symbol(x, Decl(controlFlowArrays.ts, 74, 7)) + + x.push(5); +>x.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 74, 7)) +>push : Symbol(Array.push, Decl(lib.d.ts, --, --)) + + if (cond()) return x; // number[] +>cond : Symbol(cond, Decl(controlFlowArrays.ts, 0, 0)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 74, 7)) + + x.push("hello"); +>x.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 74, 7)) +>push : Symbol(Array.push, Decl(lib.d.ts, --, --)) + + if (cond()) return x; // (string | number)[] +>cond : Symbol(cond, Decl(controlFlowArrays.ts, 0, 0)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 74, 7)) + + x.push(true); +>x.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 74, 7)) +>push : Symbol(Array.push, Decl(lib.d.ts, --, --)) + + return x; // (string | number | boolean)[] +>x : Symbol(x, Decl(controlFlowArrays.ts, 74, 7)) +} + +function f9() { +>f9 : Symbol(f9, Decl(controlFlowArrays.ts, 81, 1)) + + let x = []; +>x : Symbol(x, Decl(controlFlowArrays.ts, 84, 7)) + + if (cond()) { +>cond : Symbol(cond, Decl(controlFlowArrays.ts, 0, 0)) + + x.push(5); +>x.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 84, 7)) +>push : Symbol(Array.push, Decl(lib.d.ts, --, --)) + + return x; // number[] +>x : Symbol(x, Decl(controlFlowArrays.ts, 84, 7)) + } + else { + x.push("hello"); +>x.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 84, 7)) +>push : Symbol(Array.push, Decl(lib.d.ts, --, --)) + + return x; // string[] +>x : Symbol(x, Decl(controlFlowArrays.ts, 84, 7)) + } +} + +function f10() { +>f10 : Symbol(f10, Decl(controlFlowArrays.ts, 93, 1)) + + let x = []; +>x : Symbol(x, Decl(controlFlowArrays.ts, 96, 7)) + + if (cond()) { +>cond : Symbol(cond, Decl(controlFlowArrays.ts, 0, 0)) + + x.push(true); +>x.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 96, 7)) +>push : Symbol(Array.push, Decl(lib.d.ts, --, --)) + + x; // boolean[] +>x : Symbol(x, Decl(controlFlowArrays.ts, 96, 7)) + } + else { + x.push(5); +>x.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 96, 7)) +>push : Symbol(Array.push, Decl(lib.d.ts, --, --)) + + x; // number[] +>x : Symbol(x, Decl(controlFlowArrays.ts, 96, 7)) + + while (cond()) { +>cond : Symbol(cond, Decl(controlFlowArrays.ts, 0, 0)) + + x.push("hello"); +>x.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 96, 7)) +>push : Symbol(Array.push, Decl(lib.d.ts, --, --)) + } + x; // (string | number)[] +>x : Symbol(x, Decl(controlFlowArrays.ts, 96, 7)) + } + x.push(99); +>x.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 96, 7)) +>push : Symbol(Array.push, Decl(lib.d.ts, --, --)) + + return x; // (string | number | boolean)[] +>x : Symbol(x, Decl(controlFlowArrays.ts, 96, 7)) +} + +function f11() { +>f11 : Symbol(f11, Decl(controlFlowArrays.ts, 111, 1)) + + let x = []; +>x : Symbol(x, Decl(controlFlowArrays.ts, 114, 7)) + + return x; // never[] +>x : Symbol(x, Decl(controlFlowArrays.ts, 114, 7)) +} + +function f12() { +>f12 : Symbol(f12, Decl(controlFlowArrays.ts, 116, 1)) + + let x; +>x : Symbol(x, Decl(controlFlowArrays.ts, 119, 7)) + + x = []; +>x : Symbol(x, Decl(controlFlowArrays.ts, 119, 7)) + + return x; // never[] +>x : Symbol(x, Decl(controlFlowArrays.ts, 119, 7)) +} + +function f13() { +>f13 : Symbol(f13, Decl(controlFlowArrays.ts, 122, 1)) + + var x = []; +>x : Symbol(x, Decl(controlFlowArrays.ts, 125, 7)) + + x.push(5); +>x.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 125, 7)) +>push : Symbol(Array.push, Decl(lib.d.ts, --, --)) + + x.push("hello"); +>x.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 125, 7)) +>push : Symbol(Array.push, Decl(lib.d.ts, --, --)) + + x.push(true); +>x.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 125, 7)) +>push : Symbol(Array.push, Decl(lib.d.ts, --, --)) + + return x; // (string | number | boolean)[] +>x : Symbol(x, Decl(controlFlowArrays.ts, 125, 7)) +} + +function f14() { +>f14 : Symbol(f14, Decl(controlFlowArrays.ts, 130, 1)) + + const x = []; +>x : Symbol(x, Decl(controlFlowArrays.ts, 133, 9)) + + x.push(5); +>x.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 133, 9)) +>push : Symbol(Array.push, Decl(lib.d.ts, --, --)) + + x.push("hello"); +>x.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 133, 9)) +>push : Symbol(Array.push, Decl(lib.d.ts, --, --)) + + x.push(true); +>x.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 133, 9)) +>push : Symbol(Array.push, Decl(lib.d.ts, --, --)) + + return x; // (string | number | boolean)[] +>x : Symbol(x, Decl(controlFlowArrays.ts, 133, 9)) +} diff --git a/tests/baselines/reference/controlFlowArrays.types b/tests/baselines/reference/controlFlowArrays.types new file mode 100644 index 0000000000000..7b36e4067fad8 --- /dev/null +++ b/tests/baselines/reference/controlFlowArrays.types @@ -0,0 +1,447 @@ +=== tests/cases/compiler/controlFlowArrays.ts === + +declare function cond(): boolean; +>cond : () => boolean + +function f1() { +>f1 : () => (string | number | boolean)[] + + let x = []; +>x : any[] +>[] : never[] + + x[0] = 5; +>x[0] = 5 : 5 +>x[0] : any +>x : any[] +>0 : 0 +>5 : 5 + + x[1] = "hello"; +>x[1] = "hello" : "hello" +>x[1] : any +>x : any[] +>1 : 1 +>"hello" : "hello" + + x[2] = true; +>x[2] = true : true +>x[2] : any +>x : any[] +>2 : 2 +>true : true + + return x; // (string | number | boolean)[] +>x : (string | number | boolean)[] +} + +function f2() { +>f2 : () => (string | number | boolean)[] + + let x = []; +>x : any[] +>[] : never[] + + x.push(5); +>x.push(5) : number +>x.push : (...items: any[]) => number +>x : any[] +>push : (...items: any[]) => number +>5 : 5 + + x.push("hello"); +>x.push("hello") : number +>x.push : (...items: any[]) => number +>x : any[] +>push : (...items: any[]) => number +>"hello" : "hello" + + x.push(true); +>x.push(true) : number +>x.push : (...items: any[]) => number +>x : any[] +>push : (...items: any[]) => number +>true : true + + return x; // (string | number | boolean)[] +>x : (string | number | boolean)[] +} + +function f3() { +>f3 : () => (string | number)[] + + let x; +>x : any + + x = []; +>x = [] : never[] +>x : any +>[] : never[] + + x.push(5, "hello"); +>x.push(5, "hello") : number +>x.push : (...items: any[]) => number +>x : any[] +>push : (...items: any[]) => number +>5 : 5 +>"hello" : "hello" + + return x; // (string | number)[] +>x : (string | number)[] +} + +function f4() { +>f4 : () => (string | number)[] + + let x = []; +>x : any[] +>[] : never[] + + if (cond()) { +>cond() : boolean +>cond : () => boolean + + x.push(5); +>x.push(5) : number +>x.push : (...items: any[]) => number +>x : any[] +>push : (...items: any[]) => number +>5 : 5 + } + else { + x.push("hello"); +>x.push("hello") : number +>x.push : (...items: any[]) => number +>x : any[] +>push : (...items: any[]) => number +>"hello" : "hello" + } + return x; // (string | number)[] +>x : (string | number)[] +} + +function f5() { +>f5 : () => (string | number)[] + + let x; +>x : any + + if (cond()) { +>cond() : boolean +>cond : () => boolean + + x = []; +>x = [] : never[] +>x : any +>[] : never[] + + x.push(5); +>x.push(5) : number +>x.push : (...items: any[]) => number +>x : any[] +>push : (...items: any[]) => number +>5 : 5 + } + else { + x = []; +>x = [] : never[] +>x : any +>[] : never[] + + x.push("hello"); +>x.push("hello") : number +>x.push : (...items: any[]) => number +>x : any[] +>push : (...items: any[]) => number +>"hello" : "hello" + } + return x; // (string | number)[] +>x : (string | number)[] +} + +function f6() { +>f6 : () => number | string[] + + let x; +>x : any + + if (cond()) { +>cond() : boolean +>cond : () => boolean + + x = 5; +>x = 5 : 5 +>x : any +>5 : 5 + } + else { + x = []; +>x = [] : never[] +>x : any +>[] : never[] + + x.push("hello"); +>x.push("hello") : number +>x.push : (...items: any[]) => number +>x : any[] +>push : (...items: any[]) => number +>"hello" : "hello" + } + return x; // number | string[] +>x : number | string[] +} + +function f7() { +>f7 : () => string[] | null + + let x = null; +>x : any +>null : null + + if (cond()) { +>cond() : boolean +>cond : () => boolean + + x = []; +>x = [] : never[] +>x : any +>[] : never[] + + while (cond()) { +>cond() : boolean +>cond : () => boolean + + x.push("hello"); +>x.push("hello") : number +>x.push : (...items: any[]) => number +>x : any[] +>push : (...items: any[]) => number +>"hello" : "hello" + } + } + return x; // string[] | null +>x : string[] | null +} + +function f8() { +>f8 : () => (string | number | boolean)[] + + let x = []; +>x : any[] +>[] : never[] + + x.push(5); +>x.push(5) : number +>x.push : (...items: any[]) => number +>x : any[] +>push : (...items: any[]) => number +>5 : 5 + + if (cond()) return x; // number[] +>cond() : boolean +>cond : () => boolean +>x : number[] + + x.push("hello"); +>x.push("hello") : number +>x.push : (...items: any[]) => number +>x : any[] +>push : (...items: any[]) => number +>"hello" : "hello" + + if (cond()) return x; // (string | number)[] +>cond() : boolean +>cond : () => boolean +>x : (string | number)[] + + x.push(true); +>x.push(true) : number +>x.push : (...items: any[]) => number +>x : any[] +>push : (...items: any[]) => number +>true : true + + return x; // (string | number | boolean)[] +>x : (string | number | boolean)[] +} + +function f9() { +>f9 : () => string[] | number[] + + let x = []; +>x : any[] +>[] : never[] + + if (cond()) { +>cond() : boolean +>cond : () => boolean + + x.push(5); +>x.push(5) : number +>x.push : (...items: any[]) => number +>x : any[] +>push : (...items: any[]) => number +>5 : 5 + + return x; // number[] +>x : number[] + } + else { + x.push("hello"); +>x.push("hello") : number +>x.push : (...items: any[]) => number +>x : any[] +>push : (...items: any[]) => number +>"hello" : "hello" + + return x; // string[] +>x : string[] + } +} + +function f10() { +>f10 : () => (string | number | boolean)[] + + let x = []; +>x : any[] +>[] : never[] + + if (cond()) { +>cond() : boolean +>cond : () => boolean + + x.push(true); +>x.push(true) : number +>x.push : (...items: any[]) => number +>x : any[] +>push : (...items: any[]) => number +>true : true + + x; // boolean[] +>x : boolean[] + } + else { + x.push(5); +>x.push(5) : number +>x.push : (...items: any[]) => number +>x : any[] +>push : (...items: any[]) => number +>5 : 5 + + x; // number[] +>x : number[] + + while (cond()) { +>cond() : boolean +>cond : () => boolean + + x.push("hello"); +>x.push("hello") : number +>x.push : (...items: any[]) => number +>x : any[] +>push : (...items: any[]) => number +>"hello" : "hello" + } + x; // (string | number)[] +>x : (string | number)[] + } + x.push(99); +>x.push(99) : number +>x.push : (...items: any[]) => number +>x : any[] +>push : (...items: any[]) => number +>99 : 99 + + return x; // (string | number | boolean)[] +>x : (string | number | boolean)[] +} + +function f11() { +>f11 : () => never[] + + let x = []; +>x : any[] +>[] : never[] + + return x; // never[] +>x : never[] +} + +function f12() { +>f12 : () => never[] + + let x; +>x : any + + x = []; +>x = [] : never[] +>x : any +>[] : never[] + + return x; // never[] +>x : never[] +} + +function f13() { +>f13 : () => (string | number | boolean)[] + + var x = []; +>x : any[] +>[] : never[] + + x.push(5); +>x.push(5) : number +>x.push : (...items: any[]) => number +>x : any[] +>push : (...items: any[]) => number +>5 : 5 + + x.push("hello"); +>x.push("hello") : number +>x.push : (...items: any[]) => number +>x : any[] +>push : (...items: any[]) => number +>"hello" : "hello" + + x.push(true); +>x.push(true) : number +>x.push : (...items: any[]) => number +>x : any[] +>push : (...items: any[]) => number +>true : true + + return x; // (string | number | boolean)[] +>x : (string | number | boolean)[] +} + +function f14() { +>f14 : () => (string | number | boolean)[] + + const x = []; +>x : any[] +>[] : never[] + + x.push(5); +>x.push(5) : number +>x.push : (...items: any[]) => number +>x : any[] +>push : (...items: any[]) => number +>5 : 5 + + x.push("hello"); +>x.push("hello") : number +>x.push : (...items: any[]) => number +>x : any[] +>push : (...items: any[]) => number +>"hello" : "hello" + + x.push(true); +>x.push(true) : number +>x.push : (...items: any[]) => number +>x : any[] +>push : (...items: any[]) => number +>true : true + + return x; // (string | number | boolean)[] +>x : (string | number | boolean)[] +} From 12906a74dec4337b55a8be37dc8b29cdd84fea2a Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 6 Oct 2016 16:55:08 -0700 Subject: [PATCH 07/22] Fix some minor issues --- src/compiler/checker.ts | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f95ff1ccfec4c..0a606afe69fe7 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8510,12 +8510,12 @@ namespace ts { } function isEvolvingArrayType(type: Type) { - return type.flags & TypeFlags.Anonymous && !!(type).elementType; + return !!(type.flags & TypeFlags.Anonymous && (type).elementType); } function createFinalArrayType(elementType: Type) { return createArrayType(elementType !== neverType ? - getUnionType([elementType], /*subtypeReduction*/ true) : + elementType.flags & TypeFlags.Union ? getUnionType((elementType).types, /*subtypeReduction*/ true) : elementType : strictNullChecks ? neverType : undefinedWideningType); } @@ -8528,16 +8528,29 @@ namespace ts { return isEvolvingArrayType(type) ? getFinalArrayType(type) : type; } - function getElementTypeOfEvolvingArrayType(evolvingArrayType: AnonymousType) { - return evolvingArrayType.elementType; + function getElementTypeOfEvolvingArrayType(type: Type) { + return isEvolvingArrayType(type) ? (type).elementType : neverType; + } + + function isEvolvingArrayTypeList(types: Type[]) { + let hasEvolvingArrayType = false; + for (const t of types) { + if (!(t.flags & TypeFlags.Never)) { + if (!isEvolvingArrayType(t)) { + return false; + } + hasEvolvingArrayType = true; + } + } + return hasEvolvingArrayType; } // At flow control branch or loop junctions, if the type along every antecedent code path // is an evolving array type, we construct a combined evolving array type. Otherwise we // finalize all evolving array types. function getUnionOrEvolvingArrayType(types: Type[], subtypeReduction: boolean) { - return types.length && every(types, isEvolvingArrayType) ? - getEvolvingArrayType(getUnionType(map(types, getElementTypeOfEvolvingArrayType))) : + return isEvolvingArrayTypeList(types) ? + getEvolvingArrayType(getUnionType(map(types, getElementTypeOfEvolvingArrayType))) : getUnionType(sameMap(types, finalizeEvolvingArrayType), subtypeReduction); } @@ -9211,6 +9224,10 @@ namespace ts { } } + function isConstVariable(symbol: Symbol) { + return symbol.flags & SymbolFlags.Variable && (getDeclarationNodeFlagsFromSymbol(symbol) & NodeFlags.Const) !== 0 && getTypeOfSymbol(symbol) !== autoArrayType; + } + function checkIdentifier(node: Identifier): Type { const symbol = getResolvedSymbol(node); @@ -9303,7 +9320,7 @@ namespace ts { // analysis to include the immediately enclosing function. while (flowContainer !== declarationContainer && (flowContainer.kind === SyntaxKind.FunctionExpression || flowContainer.kind === SyntaxKind.ArrowFunction) && - (isReadonlySymbol(localOrExportSymbol) || isParameter && !isParameterAssigned(localOrExportSymbol))) { + (isConstVariable(localOrExportSymbol) || isParameter && !isParameterAssigned(localOrExportSymbol))) { flowContainer = getControlFlowContainer(flowContainer); } // We only look for uninitialized variables in strict null checking mode, and only when we can analyze From 2f5af2e04b995873b89fb3cc8744fbdfa36a1a8f Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 6 Oct 2016 17:02:33 -0700 Subject: [PATCH 08/22] Add additional test --- .../reference/controlFlowArrayErrors.errors.txt | 16 +++++++++++++++- .../reference/controlFlowArrayErrors.js | 15 +++++++++++++++ tests/cases/compiler/controlFlowArrayErrors.ts | 8 ++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/tests/baselines/reference/controlFlowArrayErrors.errors.txt b/tests/baselines/reference/controlFlowArrayErrors.errors.txt index 7475c27324349..bc13de261e222 100644 --- a/tests/baselines/reference/controlFlowArrayErrors.errors.txt +++ b/tests/baselines/reference/controlFlowArrayErrors.errors.txt @@ -6,9 +6,11 @@ tests/cases/compiler/controlFlowArrayErrors.ts(30,12): error TS2345: Argument of tests/cases/compiler/controlFlowArrayErrors.ts(35,12): error TS2345: Argument of type 'true' is not assignable to parameter of type 'string | number'. tests/cases/compiler/controlFlowArrayErrors.ts(49,5): error TS2349: Cannot invoke an expression whose type lacks a call signature. Type '((...items: (string | number)[]) => number) | ((...items: boolean[]) => number)' has no compatible call signatures. tests/cases/compiler/controlFlowArrayErrors.ts(57,12): error TS2345: Argument of type '"hello"' is not assignable to parameter of type 'number'. +tests/cases/compiler/controlFlowArrayErrors.ts(61,11): error TS7034: Variable 'x' implicitly has type 'any[]' in some locations where its type cannot be determined. +tests/cases/compiler/controlFlowArrayErrors.ts(64,9): error TS7005: Variable 'x' implicitly has an 'any[]' type. -==== tests/cases/compiler/controlFlowArrayErrors.ts (8 errors) ==== +==== tests/cases/compiler/controlFlowArrayErrors.ts (10 errors) ==== declare function cond(): boolean; @@ -82,4 +84,16 @@ tests/cases/compiler/controlFlowArrayErrors.ts(57,12): error TS2345: Argument of y.push("hello"); // Error ~~~~~~~ !!! error TS2345: Argument of type '"hello"' is not assignable to parameter of type 'number'. + } + + function f8() { + const x = []; // Implicit any[] error in some locations + ~ +!!! error TS7034: Variable 'x' implicitly has type 'any[]' in some locations where its type cannot be determined. + x.push(5); + function g() { + x; // Implicit any[] error + ~ +!!! error TS7005: Variable 'x' implicitly has an 'any[]' type. + } } \ No newline at end of file diff --git a/tests/baselines/reference/controlFlowArrayErrors.js b/tests/baselines/reference/controlFlowArrayErrors.js index c1589449334c2..538addc31c029 100644 --- a/tests/baselines/reference/controlFlowArrayErrors.js +++ b/tests/baselines/reference/controlFlowArrayErrors.js @@ -56,6 +56,14 @@ function f7() { let y = x; // y has non-evolving array value x.push("hello"); // Ok y.push("hello"); // Error +} + +function f8() { + const x = []; // Implicit any[] error in some locations + x.push(5); + function g() { + x; // Implicit any[] error + } } //// [controlFlowArrayErrors.js] @@ -108,3 +116,10 @@ function f7() { x.push("hello"); // Ok y.push("hello"); // Error } +function f8() { + var x = []; // Implicit any[] error in some locations + x.push(5); + function g() { + x; // Implicit any[] error + } +} diff --git a/tests/cases/compiler/controlFlowArrayErrors.ts b/tests/cases/compiler/controlFlowArrayErrors.ts index 84da65c41dc5b..aab3a974cd808 100644 --- a/tests/cases/compiler/controlFlowArrayErrors.ts +++ b/tests/cases/compiler/controlFlowArrayErrors.ts @@ -56,4 +56,12 @@ function f7() { let y = x; // y has non-evolving array value x.push("hello"); // Ok y.push("hello"); // Error +} + +function f8() { + const x = []; // Implicit any[] error in some locations + x.push(5); + function g() { + x; // Implicit any[] error + } } \ No newline at end of file From 71b9b3351755e3e6978cec656c91032c0bfb0ab1 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 8 Oct 2016 15:26:17 -0700 Subject: [PATCH 09/22] Fix issue in control flow analysis of nested loops --- src/compiler/checker.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0a606afe69fe7..2ff5700082a69 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8797,10 +8797,14 @@ namespace ts { } // If this flow loop junction and reference are already being processed, return // the union of the types computed for each branch so far, marked as incomplete. - // We should never see an empty array here because the first antecedent of a loop - // junction is always the non-looping control flow path that leads to the top. + // It is possible to see an empty array in cases where loops are nested and the + // back edge of the outer loop reaches an inner loop that is already being analyzed. + // In such cases we restart the analysis of the inner loop, which will then see + // a non-empty in-process array for the outer loop and eventually terminate because + // the first antecedent of a loop junction is always the non-looping control flow + // path that leads to the top. for (let i = flowLoopStart; i < flowLoopCount; i++) { - if (flowLoopNodes[i] === flow && flowLoopKeys[i] === key) { + if (flowLoopNodes[i] === flow && flowLoopKeys[i] === key && flowLoopTypes[i].length) { return createFlowType(getUnionOrEvolvingArrayType(flowLoopTypes[i], /*subtypeReduction*/ false), /*incomplete*/ true); } } From d202b1c037634939003b88fec921465969dec028 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 8 Oct 2016 15:27:49 -0700 Subject: [PATCH 10/22] Add test --- tests/cases/compiler/controlFlowArrays.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/cases/compiler/controlFlowArrays.ts b/tests/cases/compiler/controlFlowArrays.ts index d5eaa9c4320b6..781b8c09d126b 100644 --- a/tests/cases/compiler/controlFlowArrays.ts +++ b/tests/cases/compiler/controlFlowArrays.ts @@ -138,4 +138,13 @@ function f14() { x.push("hello"); x.push(true); return x; // (string | number | boolean)[] +} + +function f15() { + let x = []; + while (cond()) { + while (cond()) {} + x.push("hello"); + } + return x; // string[] } \ No newline at end of file From 89826a52ad09a5c854670caff7960e83a71fae43 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 8 Oct 2016 15:27:57 -0700 Subject: [PATCH 11/22] Accept new baselines --- .../baselines/reference/controlFlowArrays.js | 17 ++++++++++++ .../reference/controlFlowArrays.symbols | 21 +++++++++++++++ .../reference/controlFlowArrays.types | 26 +++++++++++++++++++ 3 files changed, 64 insertions(+) diff --git a/tests/baselines/reference/controlFlowArrays.js b/tests/baselines/reference/controlFlowArrays.js index 26885aecd8bd9..c5ec1a7642c3c 100644 --- a/tests/baselines/reference/controlFlowArrays.js +++ b/tests/baselines/reference/controlFlowArrays.js @@ -137,6 +137,15 @@ function f14() { x.push("hello"); x.push(true); return x; // (string | number | boolean)[] +} + +function f15() { + let x = []; + while (cond()) { + while (cond()) {} + x.push("hello"); + } + return x; // string[] } //// [controlFlowArrays.js] @@ -265,3 +274,11 @@ function f14() { x.push(true); return x; // (string | number | boolean)[] } +function f15() { + var x = []; + while (cond()) { + while (cond()) { } + x.push("hello"); + } + return x; // string[] +} diff --git a/tests/baselines/reference/controlFlowArrays.symbols b/tests/baselines/reference/controlFlowArrays.symbols index 5b5ef7f850617..fd7c6ba89b7eb 100644 --- a/tests/baselines/reference/controlFlowArrays.symbols +++ b/tests/baselines/reference/controlFlowArrays.symbols @@ -348,3 +348,24 @@ function f14() { return x; // (string | number | boolean)[] >x : Symbol(x, Decl(controlFlowArrays.ts, 133, 9)) } + +function f15() { +>f15 : Symbol(f15, Decl(controlFlowArrays.ts, 138, 1)) + + let x = []; +>x : Symbol(x, Decl(controlFlowArrays.ts, 141, 7)) + + while (cond()) { +>cond : Symbol(cond, Decl(controlFlowArrays.ts, 0, 0)) + + while (cond()) {} +>cond : Symbol(cond, Decl(controlFlowArrays.ts, 0, 0)) + + x.push("hello"); +>x.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 141, 7)) +>push : Symbol(Array.push, Decl(lib.d.ts, --, --)) + } + return x; // string[] +>x : Symbol(x, Decl(controlFlowArrays.ts, 141, 7)) +} diff --git a/tests/baselines/reference/controlFlowArrays.types b/tests/baselines/reference/controlFlowArrays.types index 7b36e4067fad8..3fb84215ed70f 100644 --- a/tests/baselines/reference/controlFlowArrays.types +++ b/tests/baselines/reference/controlFlowArrays.types @@ -445,3 +445,29 @@ function f14() { return x; // (string | number | boolean)[] >x : (string | number | boolean)[] } + +function f15() { +>f15 : () => string[] + + let x = []; +>x : any[] +>[] : never[] + + while (cond()) { +>cond() : boolean +>cond : () => boolean + + while (cond()) {} +>cond() : boolean +>cond : () => boolean + + x.push("hello"); +>x.push("hello") : number +>x.push : (...items: any[]) => number +>x : any[] +>push : (...items: any[]) => number +>"hello" : "hello" + } + return x; // string[] +>x : string[] +} From e6b588a956d5046469dbfa240d35ab1cc09c0046 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 11 Oct 2016 10:14:06 -0700 Subject: [PATCH 12/22] Support parentheses and comma operator with evolving arrays --- src/compiler/binder.ts | 10 +++++----- src/compiler/checker.ts | 34 ++++++++++++++++++++++++---------- src/compiler/types.ts | 2 +- 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 1b121394a8cb5..6926403d9bbad 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -760,7 +760,7 @@ namespace ts { }; } - function createFlowArrayMutation(antecedent: FlowNode, node: Expression): FlowNode { + function createFlowArrayMutation(antecedent: FlowNode, node: CallExpression | BinaryExpression): FlowNode { setFlowNodeReferenced(antecedent); return { flags: FlowFlags.ArrayMutation, @@ -1151,8 +1151,8 @@ namespace ts { bindAssignmentTargetFlow(node.left); if (node.left.kind === SyntaxKind.ElementAccessExpression) { const elementAccess = node.left; - if (isNarrowableReference(elementAccess.expression)) { - currentFlow = createFlowArrayMutation(currentFlow, elementAccess.expression); + if (isNarrowableOperand(elementAccess.expression)) { + currentFlow = createFlowArrayMutation(currentFlow, node); } } } @@ -1217,8 +1217,8 @@ namespace ts { } if (node.expression.kind === SyntaxKind.PropertyAccessExpression) { const propertyAccess = node.expression; - if (isNarrowableReference(propertyAccess.expression) && propertyAccess.name.text === "push") { - currentFlow = createFlowArrayMutation(currentFlow, propertyAccess.expression); + if (isNarrowableOperand(propertyAccess.expression) && propertyAccess.name.text === "push") { + currentFlow = createFlowArrayMutation(currentFlow, node); } } } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2ff5700082a69..379d61850e48a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8424,6 +8424,14 @@ namespace ts { return node; } + function getReferenceParent(node: Node): Node { + const parent = node.parent; + return parent.kind === SyntaxKind.ParenthesizedExpression || + parent.kind === SyntaxKind.BinaryExpression && (parent).operatorToken.kind === SyntaxKind.EqualsToken && (parent).left === node || + parent.kind === SyntaxKind.BinaryExpression && (parent).operatorToken.kind === SyntaxKind.CommaToken && (parent).right === node ? + getReferenceParent(parent) : parent; + } + function getTypeOfSwitchClause(clause: CaseClause | DefaultClause) { if (clause.kind === SyntaxKind.CaseClause) { const caseType = getRegularTypeOfLiteralType(checkExpression((clause).expression)); @@ -8556,15 +8564,16 @@ namespace ts { // Return true if the given node is 'x' in an 'x.push(value)' operation. function isPushCallTarget(node: Node) { - return node.parent.kind === SyntaxKind.PropertyAccessExpression && - (node.parent).name.text === "push" && - node.parent.parent.kind === SyntaxKind.CallExpression; + const parent = getReferenceParent(node); + return parent.kind === SyntaxKind.PropertyAccessExpression && + (parent).name.text === "push" && + parent.parent.kind === SyntaxKind.CallExpression; } // Return true if the given node is 'x' in an 'x[n] = value' operation, where 'n' is an // expression of type any, undefined, or a number-like type. function isElementAssignmentTarget(node: Node) { - const parent = node.parent; + const parent = getReferenceParent(node); return parent.kind === SyntaxKind.ElementAccessExpression && (parent).expression === node && parent.parent.kind === SyntaxKind.BinaryExpression && @@ -8696,19 +8705,24 @@ namespace ts { function getTypeAtFlowArrayMutation(flow: FlowArrayMutation): FlowType { const node = flow.node; - if (isMatchingReference(reference, node)) { + const expr = node.kind === SyntaxKind.CallExpression ? + ((node).expression).expression : + ((node).left).expression; + if (isMatchingReference(reference, getReferenceCandidate(expr))) { const flowType = getTypeAtFlowNode(flow.antecedent); const type = getTypeFromFlowType(flowType); if (isEvolvingArrayType(type)) { - const parent = node.parent; let evolvedType = type; - if (parent.kind === SyntaxKind.PropertyAccessExpression) { - for (const arg of (parent.parent).arguments) { + if (node.kind === SyntaxKind.CallExpression) { + for (const arg of (node).arguments) { evolvedType = addEvolvingArrayElementType(evolvedType, arg); } } - else if (isTypeAnyOrAllConstituentTypesHaveKind(checkExpression((parent).argumentExpression), TypeFlags.NumberLike)) { - evolvedType = addEvolvingArrayElementType(evolvedType, (parent.parent).right); + else { + const indexType = checkExpression(((node).left).argumentExpression); + if (isTypeAnyOrAllConstituentTypesHaveKind(indexType, TypeFlags.NumberLike | TypeFlags.Undefined)) { + evolvedType = addEvolvingArrayElementType(evolvedType, (node).right); + } } return evolvedType === type ? flowType : createFlowType(evolvedType, isIncomplete(flowType)); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 9759c2ab540ca..5c32649d97ccf 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1955,7 +1955,7 @@ namespace ts { // FlowArrayMutation represents a node potentially mutates an array, i.e. an // operation of the form 'x.push(value)' or 'x[n] = value'. export interface FlowArrayMutation extends FlowNode { - node: Expression; + node: CallExpression | BinaryExpression; antecedent: FlowNode; } From 612ed1e24a0932b005a57d27092e39711046be10 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 11 Oct 2016 10:27:40 -0700 Subject: [PATCH 13/22] Fix minor issue --- src/compiler/checker.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 379d61850e48a..9e2b7a31748d1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8424,12 +8424,12 @@ namespace ts { return node; } - function getReferenceParent(node: Node): Node { + function getReferenceRoot(node: Node): Node { const parent = node.parent; return parent.kind === SyntaxKind.ParenthesizedExpression || parent.kind === SyntaxKind.BinaryExpression && (parent).operatorToken.kind === SyntaxKind.EqualsToken && (parent).left === node || parent.kind === SyntaxKind.BinaryExpression && (parent).operatorToken.kind === SyntaxKind.CommaToken && (parent).right === node ? - getReferenceParent(parent) : parent; + getReferenceRoot(parent) : node; } function getTypeOfSwitchClause(clause: CaseClause | DefaultClause) { @@ -8564,7 +8564,7 @@ namespace ts { // Return true if the given node is 'x' in an 'x.push(value)' operation. function isPushCallTarget(node: Node) { - const parent = getReferenceParent(node); + const parent = getReferenceRoot(node).parent; return parent.kind === SyntaxKind.PropertyAccessExpression && (parent).name.text === "push" && parent.parent.kind === SyntaxKind.CallExpression; @@ -8573,9 +8573,10 @@ namespace ts { // Return true if the given node is 'x' in an 'x[n] = value' operation, where 'n' is an // expression of type any, undefined, or a number-like type. function isElementAssignmentTarget(node: Node) { - const parent = getReferenceParent(node); + const root = getReferenceRoot(node); + const parent = root.parent; return parent.kind === SyntaxKind.ElementAccessExpression && - (parent).expression === node && + (parent).expression === root && parent.parent.kind === SyntaxKind.BinaryExpression && (parent.parent).operatorToken.kind === SyntaxKind.EqualsToken && (parent.parent).left === parent && From e9858de363e22b0f0318ef8b2848306e944aff50 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 11 Oct 2016 10:28:13 -0700 Subject: [PATCH 14/22] Add test --- tests/cases/compiler/controlFlowArrays.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/cases/compiler/controlFlowArrays.ts b/tests/cases/compiler/controlFlowArrays.ts index 781b8c09d126b..e89a2443753fa 100644 --- a/tests/cases/compiler/controlFlowArrays.ts +++ b/tests/cases/compiler/controlFlowArrays.ts @@ -147,4 +147,13 @@ function f15() { x.push("hello"); } return x; // string[] +} + +function f16() { + let x; + let y; + (x = [], x).push(5); + (x.push("hello"), x).push(true); + ((x))[3] = { a: 1 }; + return x; // (string | number | boolean | { a: number })[] } \ No newline at end of file From c9e2f959184beda9e25ee97d186a5dea0f44bb88 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 11 Oct 2016 10:32:00 -0700 Subject: [PATCH 15/22] Accept new baselines --- .../baselines/reference/controlFlowArrays.js | 17 +++++++ .../reference/controlFlowArrays.symbols | 31 ++++++++++++ .../reference/controlFlowArrays.types | 50 +++++++++++++++++++ 3 files changed, 98 insertions(+) diff --git a/tests/baselines/reference/controlFlowArrays.js b/tests/baselines/reference/controlFlowArrays.js index c5ec1a7642c3c..d6f387ab9815a 100644 --- a/tests/baselines/reference/controlFlowArrays.js +++ b/tests/baselines/reference/controlFlowArrays.js @@ -146,6 +146,15 @@ function f15() { x.push("hello"); } return x; // string[] +} + +function f16() { + let x; + let y; + (x = [], x).push(5); + (x.push("hello"), x).push(true); + ((x))[3] = { a: 1 }; + return x; // (string | number | boolean | { a: number })[] } //// [controlFlowArrays.js] @@ -282,3 +291,11 @@ function f15() { } return x; // string[] } +function f16() { + var x; + var y; + (x = [], x).push(5); + (x.push("hello"), x).push(true); + ((x))[3] = { a: 1 }; + return x; // (string | number | boolean | { a: number })[] +} diff --git a/tests/baselines/reference/controlFlowArrays.symbols b/tests/baselines/reference/controlFlowArrays.symbols index fd7c6ba89b7eb..204a2171992e4 100644 --- a/tests/baselines/reference/controlFlowArrays.symbols +++ b/tests/baselines/reference/controlFlowArrays.symbols @@ -369,3 +369,34 @@ function f15() { return x; // string[] >x : Symbol(x, Decl(controlFlowArrays.ts, 141, 7)) } + +function f16() { +>f16 : Symbol(f16, Decl(controlFlowArrays.ts, 147, 1)) + + let x; +>x : Symbol(x, Decl(controlFlowArrays.ts, 150, 7)) + + let y; +>y : Symbol(y, Decl(controlFlowArrays.ts, 151, 7)) + + (x = [], x).push(5); +>(x = [], x).push : Symbol(Array.push, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 150, 7)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 150, 7)) +>push : Symbol(Array.push, Decl(lib.d.ts, --, --)) + + (x.push("hello"), x).push(true); +>(x.push("hello"), x).push : Symbol(Array.push, Decl(lib.d.ts, --, --)) +>x.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 150, 7)) +>push : Symbol(Array.push, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 150, 7)) +>push : Symbol(Array.push, Decl(lib.d.ts, --, --)) + + ((x))[3] = { a: 1 }; +>x : Symbol(x, Decl(controlFlowArrays.ts, 150, 7)) +>a : Symbol(a, Decl(controlFlowArrays.ts, 154, 16)) + + return x; // (string | number | boolean | { a: number })[] +>x : Symbol(x, Decl(controlFlowArrays.ts, 150, 7)) +} diff --git a/tests/baselines/reference/controlFlowArrays.types b/tests/baselines/reference/controlFlowArrays.types index 3fb84215ed70f..3a91ce7e1972e 100644 --- a/tests/baselines/reference/controlFlowArrays.types +++ b/tests/baselines/reference/controlFlowArrays.types @@ -471,3 +471,53 @@ function f15() { return x; // string[] >x : string[] } + +function f16() { +>f16 : () => (string | number | boolean | { a: number; })[] + + let x; +>x : any + + let y; +>y : any + + (x = [], x).push(5); +>(x = [], x).push(5) : number +>(x = [], x).push : (...items: any[]) => number +>(x = [], x) : any[] +>x = [], x : any[] +>x = [] : never[] +>x : any +>[] : never[] +>x : any[] +>push : (...items: any[]) => number +>5 : 5 + + (x.push("hello"), x).push(true); +>(x.push("hello"), x).push(true) : number +>(x.push("hello"), x).push : (...items: any[]) => number +>(x.push("hello"), x) : any[] +>x.push("hello"), x : any[] +>x.push("hello") : number +>x.push : (...items: any[]) => number +>x : any[] +>push : (...items: any[]) => number +>"hello" : "hello" +>x : any[] +>push : (...items: any[]) => number +>true : true + + ((x))[3] = { a: 1 }; +>((x))[3] = { a: 1 } : { a: number; } +>((x))[3] : any +>((x)) : any[] +>(x) : any[] +>x : any[] +>3 : 3 +>{ a: 1 } : { a: number; } +>a : number +>1 : 1 + + return x; // (string | number | boolean | { a: number })[] +>x : (string | number | boolean | { a: number; })[] +} From 38278ee0788bb8dc26b5052f103591b8fd0be0ab Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 11 Oct 2016 18:28:19 -0700 Subject: [PATCH 16/22] Fix typo --- src/compiler/core.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 36548334a62b5..47e533c9a6b48 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -271,7 +271,7 @@ namespace ts { return result; } - // Maps from T to T and avoids allocation of all elements map to themselves + // Maps from T to T and avoids allocation if all elements map to themselves export function sameMap(array: T[], f: (x: T, i: number) => T): T[] { let result: T[]; if (array) { From 1dedca73d120e396fa710c782d2a3bf59252d732 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 13 Oct 2016 09:43:55 -0700 Subject: [PATCH 17/22] Support 'unshift' and fix typo --- src/compiler/binder.ts | 2 +- src/compiler/checker.ts | 18 ++++++++++-------- src/compiler/types.ts | 2 +- src/compiler/utilities.ts | 4 ++++ 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 6926403d9bbad..65d620f92336c 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1217,7 +1217,7 @@ namespace ts { } if (node.expression.kind === SyntaxKind.PropertyAccessExpression) { const propertyAccess = node.expression; - if (isNarrowableOperand(propertyAccess.expression) && propertyAccess.name.text === "push") { + if (isNarrowableOperand(propertyAccess.expression) && isPushOrUnshiftIdentifier(propertyAccess.name)) { currentFlow = createFlowArrayMutation(currentFlow, node); } } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9e2b7a31748d1..7bec343c91e7d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8405,8 +8405,10 @@ namespace ts { } function isEmptyArrayAssignment(node: VariableDeclaration | BindingElement | Expression) { - return node.kind === SyntaxKind.VariableDeclaration && (node).initializer && isEmptyArrayLiteral((node).initializer) || - node.kind !== SyntaxKind.BindingElement && node.parent.kind === SyntaxKind.BinaryExpression && isEmptyArrayLiteral((node.parent).right); + return node.kind === SyntaxKind.VariableDeclaration && (node).initializer && + isEmptyArrayLiteral((node).initializer) || + node.kind !== SyntaxKind.BindingElement && node.parent.kind === SyntaxKind.BinaryExpression && + isEmptyArrayLiteral((node.parent).right); } function getReferenceCandidate(node: Expression): Expression { @@ -8562,12 +8564,12 @@ namespace ts { getUnionType(sameMap(types, finalizeEvolvingArrayType), subtypeReduction); } - // Return true if the given node is 'x' in an 'x.push(value)' operation. - function isPushCallTarget(node: Node) { + // Return true if the given node is 'x' in an 'x.push(value)' or 'x.unshift(value)' operation. + function isPushOrUnshiftCallTarget(node: Node) { const parent = getReferenceRoot(node).parent; return parent.kind === SyntaxKind.PropertyAccessExpression && - (parent).name.text === "push" && - parent.parent.kind === SyntaxKind.CallExpression; + parent.parent.kind === SyntaxKind.CallExpression && + isPushOrUnshiftIdentifier((parent).name); } // Return true if the given node is 'x' in an 'x[n] = value' operation, where 'n' is an @@ -8596,9 +8598,9 @@ namespace ts { const evolvedType = getTypeFromFlowType(getTypeAtFlowNode(reference.flowNode)); visitedFlowCount = visitedFlowStart; // When the reference is 'x' in an 'x.push(value)' or 'x[n] = value' operation, we give type - // 'any[]' to 'x' instead of using the type determed by control flow analysis such that new + // 'any[]' to 'x' instead of using the type determined by control flow analysis such that new // element types are not considered errors. - const isEvolvingArrayInferenceTarget = isEvolvingArrayType(evolvedType) && (isPushCallTarget(reference) || isElementAssignmentTarget(reference)); + const isEvolvingArrayInferenceTarget = isEvolvingArrayType(evolvedType) && (isPushOrUnshiftCallTarget(reference) || isElementAssignmentTarget(reference)); const resultType = isEvolvingArrayInferenceTarget ? anyArrayType : finalizeEvolvingArrayType(evolvedType); if (reference.parent.kind === SyntaxKind.NonNullExpression && getTypeWithFacts(resultType, TypeFacts.NEUndefinedOrNull).flags & TypeFlags.Never) { return declaredType; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 5c32649d97ccf..ff7618c8e3759 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1953,7 +1953,7 @@ namespace ts { } // FlowArrayMutation represents a node potentially mutates an array, i.e. an - // operation of the form 'x.push(value)' or 'x[n] = value'. + // operation of the form 'x.push(value)', 'x.unshift(value)' or 'x[n] = value'. export interface FlowArrayMutation extends FlowNode { node: CallExpression | BinaryExpression; antecedent: FlowNode; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 558015bb0e3ae..6e7dd8702f7c3 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1895,6 +1895,10 @@ namespace ts { return node.kind === SyntaxKind.Identifier && (node).text === "Symbol"; } + export function isPushOrUnshiftIdentifier(node: Identifier) { + return node.text === "push" || node.text === "unshift"; + } + export function isModifierKind(token: SyntaxKind): boolean { switch (token) { case SyntaxKind.AbstractKeyword: From 620b3f91e11e00a65ad024127d8964988875922a Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 13 Oct 2016 09:44:37 -0700 Subject: [PATCH 18/22] Fix test --- tests/baselines/reference/implicitAnyWidenToAny.errors.txt | 2 +- tests/baselines/reference/implicitAnyWidenToAny.js | 4 ++-- tests/cases/compiler/implicitAnyWidenToAny.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/baselines/reference/implicitAnyWidenToAny.errors.txt b/tests/baselines/reference/implicitAnyWidenToAny.errors.txt index 7a9314d982f96..4302211d9a0eb 100644 --- a/tests/baselines/reference/implicitAnyWidenToAny.errors.txt +++ b/tests/baselines/reference/implicitAnyWidenToAny.errors.txt @@ -8,7 +8,7 @@ tests/cases/compiler/implicitAnyWidenToAny.ts(4,5): error TS7005: Variable 'wide var widenArray = [null, undefined]; // error at "widenArray" ~~~~~~~~~~ !!! error TS7005: Variable 'widenArray' implicitly has an 'any[]' type. - var emptyArray = []; // error at "emptyArray" + var emptyArray = []; // these should not be error class AnimalObj { diff --git a/tests/baselines/reference/implicitAnyWidenToAny.js b/tests/baselines/reference/implicitAnyWidenToAny.js index 0779a16674479..0d1c42ad9d049 100644 --- a/tests/baselines/reference/implicitAnyWidenToAny.js +++ b/tests/baselines/reference/implicitAnyWidenToAny.js @@ -3,7 +3,7 @@ var x = null; // error at "x" var x1 = undefined; // error at "x1" var widenArray = [null, undefined]; // error at "widenArray" -var emptyArray = []; // error at "emptyArray" +var emptyArray = []; // these should not be error class AnimalObj { @@ -32,7 +32,7 @@ var obj1 = anyReturnFunc(); var x = null; // error at "x" var x1 = undefined; // error at "x1" var widenArray = [null, undefined]; // error at "widenArray" -var emptyArray = []; // error at "emptyArray" +var emptyArray = []; // these should not be error var AnimalObj = (function () { function AnimalObj() { diff --git a/tests/cases/compiler/implicitAnyWidenToAny.ts b/tests/cases/compiler/implicitAnyWidenToAny.ts index b4f4b5eb3124c..dcbabd38b47e2 100644 --- a/tests/cases/compiler/implicitAnyWidenToAny.ts +++ b/tests/cases/compiler/implicitAnyWidenToAny.ts @@ -3,7 +3,7 @@ var x = null; // error at "x" var x1 = undefined; // error at "x1" var widenArray = [null, undefined]; // error at "widenArray" -var emptyArray = []; // error at "emptyArray" +var emptyArray = []; // these should not be error class AnimalObj { From a27a68f8eb67557a9a2eda50501aa0c40c20699f Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 13 Oct 2016 09:44:50 -0700 Subject: [PATCH 19/22] Add additional tests --- .../baselines/reference/controlFlowArrays.js | 30 +++++++++ .../reference/controlFlowArrays.symbols | 48 ++++++++++++++ .../reference/controlFlowArrays.types | 64 +++++++++++++++++++ tests/cases/compiler/controlFlowArrays.ts | 16 +++++ 4 files changed, 158 insertions(+) diff --git a/tests/baselines/reference/controlFlowArrays.js b/tests/baselines/reference/controlFlowArrays.js index d6f387ab9815a..b2d3fa8a2f232 100644 --- a/tests/baselines/reference/controlFlowArrays.js +++ b/tests/baselines/reference/controlFlowArrays.js @@ -155,6 +155,22 @@ function f16() { (x.push("hello"), x).push(true); ((x))[3] = { a: 1 }; return x; // (string | number | boolean | { a: number })[] +} + +function f17() { + let x = []; + x.unshift(5); + x.unshift("hello"); + x.unshift(true); + return x; // (string | number | boolean)[] +} + +function f18() { + let x = []; + x.push(5); + x.unshift("hello"); + x[2] = true; + return x; // (string | number | boolean)[] } //// [controlFlowArrays.js] @@ -299,3 +315,17 @@ function f16() { ((x))[3] = { a: 1 }; return x; // (string | number | boolean | { a: number })[] } +function f17() { + var x = []; + x.unshift(5); + x.unshift("hello"); + x.unshift(true); + return x; // (string | number | boolean)[] +} +function f18() { + var x = []; + x.push(5); + x.unshift("hello"); + x[2] = true; + return x; // (string | number | boolean)[] +} diff --git a/tests/baselines/reference/controlFlowArrays.symbols b/tests/baselines/reference/controlFlowArrays.symbols index 204a2171992e4..463fb5225857d 100644 --- a/tests/baselines/reference/controlFlowArrays.symbols +++ b/tests/baselines/reference/controlFlowArrays.symbols @@ -400,3 +400,51 @@ function f16() { return x; // (string | number | boolean | { a: number })[] >x : Symbol(x, Decl(controlFlowArrays.ts, 150, 7)) } + +function f17() { +>f17 : Symbol(f17, Decl(controlFlowArrays.ts, 156, 1)) + + let x = []; +>x : Symbol(x, Decl(controlFlowArrays.ts, 159, 7)) + + x.unshift(5); +>x.unshift : Symbol(Array.unshift, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 159, 7)) +>unshift : Symbol(Array.unshift, Decl(lib.d.ts, --, --)) + + x.unshift("hello"); +>x.unshift : Symbol(Array.unshift, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 159, 7)) +>unshift : Symbol(Array.unshift, Decl(lib.d.ts, --, --)) + + x.unshift(true); +>x.unshift : Symbol(Array.unshift, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 159, 7)) +>unshift : Symbol(Array.unshift, Decl(lib.d.ts, --, --)) + + return x; // (string | number | boolean)[] +>x : Symbol(x, Decl(controlFlowArrays.ts, 159, 7)) +} + +function f18() { +>f18 : Symbol(f18, Decl(controlFlowArrays.ts, 164, 1)) + + let x = []; +>x : Symbol(x, Decl(controlFlowArrays.ts, 167, 7)) + + x.push(5); +>x.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 167, 7)) +>push : Symbol(Array.push, Decl(lib.d.ts, --, --)) + + x.unshift("hello"); +>x.unshift : Symbol(Array.unshift, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 167, 7)) +>unshift : Symbol(Array.unshift, Decl(lib.d.ts, --, --)) + + x[2] = true; +>x : Symbol(x, Decl(controlFlowArrays.ts, 167, 7)) + + return x; // (string | number | boolean)[] +>x : Symbol(x, Decl(controlFlowArrays.ts, 167, 7)) +} diff --git a/tests/baselines/reference/controlFlowArrays.types b/tests/baselines/reference/controlFlowArrays.types index 3a91ce7e1972e..2299afd7d21cd 100644 --- a/tests/baselines/reference/controlFlowArrays.types +++ b/tests/baselines/reference/controlFlowArrays.types @@ -521,3 +521,67 @@ function f16() { return x; // (string | number | boolean | { a: number })[] >x : (string | number | boolean | { a: number; })[] } + +function f17() { +>f17 : () => (string | number | boolean)[] + + let x = []; +>x : any[] +>[] : never[] + + x.unshift(5); +>x.unshift(5) : number +>x.unshift : (...items: any[]) => number +>x : any[] +>unshift : (...items: any[]) => number +>5 : 5 + + x.unshift("hello"); +>x.unshift("hello") : number +>x.unshift : (...items: any[]) => number +>x : any[] +>unshift : (...items: any[]) => number +>"hello" : "hello" + + x.unshift(true); +>x.unshift(true) : number +>x.unshift : (...items: any[]) => number +>x : any[] +>unshift : (...items: any[]) => number +>true : true + + return x; // (string | number | boolean)[] +>x : (string | number | boolean)[] +} + +function f18() { +>f18 : () => (string | number | boolean)[] + + let x = []; +>x : any[] +>[] : never[] + + x.push(5); +>x.push(5) : number +>x.push : (...items: any[]) => number +>x : any[] +>push : (...items: any[]) => number +>5 : 5 + + x.unshift("hello"); +>x.unshift("hello") : number +>x.unshift : (...items: any[]) => number +>x : any[] +>unshift : (...items: any[]) => number +>"hello" : "hello" + + x[2] = true; +>x[2] = true : true +>x[2] : any +>x : any[] +>2 : 2 +>true : true + + return x; // (string | number | boolean)[] +>x : (string | number | boolean)[] +} diff --git a/tests/cases/compiler/controlFlowArrays.ts b/tests/cases/compiler/controlFlowArrays.ts index e89a2443753fa..160f0efed1703 100644 --- a/tests/cases/compiler/controlFlowArrays.ts +++ b/tests/cases/compiler/controlFlowArrays.ts @@ -156,4 +156,20 @@ function f16() { (x.push("hello"), x).push(true); ((x))[3] = { a: 1 }; return x; // (string | number | boolean | { a: number })[] +} + +function f17() { + let x = []; + x.unshift(5); + x.unshift("hello"); + x.unshift(true); + return x; // (string | number | boolean)[] +} + +function f18() { + let x = []; + x.push(5); + x.unshift("hello"); + x[2] = true; + return x; // (string | number | boolean)[] } \ No newline at end of file From bf301e9ccce09f7dcd36944d8941e2c26e5c73f7 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 13 Oct 2016 13:28:58 -0700 Subject: [PATCH 20/22] Treat reference to empty evolving array as an implicit any[] --- src/compiler/checker.ts | 46 ++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7bec343c91e7d..9b2679adc5990 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8524,9 +8524,11 @@ namespace ts { } function createFinalArrayType(elementType: Type) { - return createArrayType(elementType !== neverType ? - elementType.flags & TypeFlags.Union ? getUnionType((elementType).types, /*subtypeReduction*/ true) : elementType : - strictNullChecks ? neverType : undefinedWideningType); + return elementType.flags & TypeFlags.Never ? + autoArrayType : + createArrayType(elementType.flags & TypeFlags.Union ? + getUnionType((elementType).types, /*subtypeReduction*/ true) : + elementType); } // We perform subtype reduction upon obtaining the final array type from an evolving array type. @@ -8564,26 +8566,22 @@ namespace ts { getUnionType(sameMap(types, finalizeEvolvingArrayType), subtypeReduction); } - // Return true if the given node is 'x' in an 'x.push(value)' or 'x.unshift(value)' operation. - function isPushOrUnshiftCallTarget(node: Node) { - const parent = getReferenceRoot(node).parent; - return parent.kind === SyntaxKind.PropertyAccessExpression && - parent.parent.kind === SyntaxKind.CallExpression && - isPushOrUnshiftIdentifier((parent).name); - } - - // Return true if the given node is 'x' in an 'x[n] = value' operation, where 'n' is an - // expression of type any, undefined, or a number-like type. - function isElementAssignmentTarget(node: Node) { + // Return true if the given node is 'x' in an 'x.length', x.push(value)', 'x.unshift(value)' or + // 'x[n] = value' operation, where 'n' is an expression of type any, undefined, or a number-like type. + function isEvolvingArrayOperationTarget(node: Node) { const root = getReferenceRoot(node); const parent = root.parent; - return parent.kind === SyntaxKind.ElementAccessExpression && + const isLengthPushOrUnshift = parent.kind === SyntaxKind.PropertyAccessExpression && ( + (parent).name.text === "length" || + parent.parent.kind === SyntaxKind.CallExpression && isPushOrUnshiftIdentifier((parent).name)); + const isElementAssignment = parent.kind === SyntaxKind.ElementAccessExpression && (parent).expression === root && parent.parent.kind === SyntaxKind.BinaryExpression && (parent.parent).operatorToken.kind === SyntaxKind.EqualsToken && (parent.parent).left === parent && !isAssignmentTarget(parent.parent) && isTypeAnyOrAllConstituentTypesHaveKind(checkExpression((parent).argumentExpression), TypeFlags.NumberLike | TypeFlags.Undefined); + return isLengthPushOrUnshift || isElementAssignment; } function getFlowTypeOfReference(reference: Node, declaredType: Type, assumeInitialized: boolean, flowContainer: Node) { @@ -8597,11 +8595,11 @@ namespace ts { const visitedFlowStart = visitedFlowCount; const evolvedType = getTypeFromFlowType(getTypeAtFlowNode(reference.flowNode)); visitedFlowCount = visitedFlowStart; - // When the reference is 'x' in an 'x.push(value)' or 'x[n] = value' operation, we give type - // 'any[]' to 'x' instead of using the type determined by control flow analysis such that new - // element types are not considered errors. - const isEvolvingArrayInferenceTarget = isEvolvingArrayType(evolvedType) && (isPushOrUnshiftCallTarget(reference) || isElementAssignmentTarget(reference)); - const resultType = isEvolvingArrayInferenceTarget ? anyArrayType : finalizeEvolvingArrayType(evolvedType); + // When the reference is 'x' in an 'x.length', 'x.push(value)', 'x.unshift(value)' or x[n] = value' operation, + // we give type 'any[]' to 'x' instead of using the type determined by control flow analysis such that operations + // on empty arrays are possible without implicit any errors and new element types can be inferred without + // type mismatch errors. + const resultType = isEvolvingArrayType(evolvedType) && isEvolvingArrayOperationTarget(reference) ? anyArrayType : finalizeEvolvingArrayType(evolvedType); if (reference.parent.kind === SyntaxKind.NonNullExpression && getTypeWithFacts(resultType, TypeFacts.NEUndefinedOrNull).flags & TypeFlags.Never) { return declaredType; } @@ -9355,12 +9353,12 @@ namespace ts { // from declaration to use, and when the variable's declared type doesn't include undefined but the // control flow based type does include undefined. if (type === autoType || type === autoArrayType) { - if (flowType === type) { + if (flowType === autoType || flowType === autoArrayType) { if (compilerOptions.noImplicitAny) { - error(declaration.name, Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined, symbolToString(symbol), typeToString(type)); - error(node, Diagnostics.Variable_0_implicitly_has_an_1_type, symbolToString(symbol), typeToString(type)); + error(declaration.name, Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined, symbolToString(symbol), typeToString(flowType)); + error(node, Diagnostics.Variable_0_implicitly_has_an_1_type, symbolToString(symbol), typeToString(flowType)); } - return convertAutoToAny(type); + return convertAutoToAny(flowType); } } else if (!assumeInitialized && !(getFalsyFlags(type) & TypeFlags.Undefined) && getFalsyFlags(flowType) & TypeFlags.Undefined) { From bfa4197ffe610354efb382a5ef772ed3bf70c3c1 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 13 Oct 2016 13:29:08 -0700 Subject: [PATCH 21/22] Update tests --- tests/cases/compiler/controlFlowArrayErrors.ts | 10 +++++----- tests/cases/compiler/controlFlowArrays.ts | 10 ++++++++-- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/tests/cases/compiler/controlFlowArrayErrors.ts b/tests/cases/compiler/controlFlowArrayErrors.ts index aab3a974cd808..08107ff365994 100644 --- a/tests/cases/compiler/controlFlowArrayErrors.ts +++ b/tests/cases/compiler/controlFlowArrayErrors.ts @@ -3,16 +3,16 @@ declare function cond(): boolean; function f1() { - let x = []; - let y = x; // Implicit any[] error + let x = []; // Implicit any[] error in some locations + let y = x; // Implicit any[] error x.push(5); let z = x; } function f2() { - let x; + let x; // Implicit any[] error in some locations x = []; - let y = x; // Implicit any[] error + let y = x; // Implicit any[] error x.push(5); let z = x; } @@ -21,7 +21,7 @@ function f3() { let x = []; // Implicit any[] error in some locations x.push(5); function g() { - x; // Implicit any[] error + x; // Implicit any[] error } } diff --git a/tests/cases/compiler/controlFlowArrays.ts b/tests/cases/compiler/controlFlowArrays.ts index 160f0efed1703..646c85f069f89 100644 --- a/tests/cases/compiler/controlFlowArrays.ts +++ b/tests/cases/compiler/controlFlowArrays.ts @@ -115,13 +115,19 @@ function f10() { function f11() { let x = []; - return x; // never[] + if (x.length === 0) { // x.length ok on implicit any[] + x.push("hello"); + } + return x; } function f12() { let x; x = []; - return x; // never[] + if (x.length === 0) { // x.length ok on implicit any[] + x.push("hello"); + } + return x; } function f13() { From 79ed3a7ed83605f0230df160d8472de1b372d4f3 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 13 Oct 2016 13:29:42 -0700 Subject: [PATCH 22/22] Accept new baselines --- .../reference/arrayLiterals2ES5.types | 6 +- .../controlFlowArrayErrors.errors.txt | 28 +++-- .../reference/controlFlowArrayErrors.js | 14 +-- .../baselines/reference/controlFlowArrays.js | 20 +++- .../reference/controlFlowArrays.symbols | 108 +++++++++++------- .../reference/controlFlowArrays.types | 40 ++++++- .../genericTypeParameterEquivalence2.types | 2 +- .../reference/globalThisCapture.types | 4 +- 8 files changed, 144 insertions(+), 78 deletions(-) diff --git a/tests/baselines/reference/arrayLiterals2ES5.types b/tests/baselines/reference/arrayLiterals2ES5.types index 833966a6e484e..c3256db925e02 100644 --- a/tests/baselines/reference/arrayLiterals2ES5.types +++ b/tests/baselines/reference/arrayLiterals2ES5.types @@ -194,9 +194,9 @@ var d5 = [...temp3]; var d6 = [...temp4]; >d6 : any[] ->[...temp4] : undefined[] ->...temp4 : undefined ->temp4 : undefined[] +>[...temp4] : any[] +>...temp4 : any +>temp4 : any[] var d7 = [...[...temp1]]; >d7 : number[] diff --git a/tests/baselines/reference/controlFlowArrayErrors.errors.txt b/tests/baselines/reference/controlFlowArrayErrors.errors.txt index bc13de261e222..2ef009dc0e115 100644 --- a/tests/baselines/reference/controlFlowArrayErrors.errors.txt +++ b/tests/baselines/reference/controlFlowArrayErrors.errors.txt @@ -1,5 +1,7 @@ -tests/cases/compiler/controlFlowArrayErrors.ts(6,9): error TS7005: Variable 'y' implicitly has an 'any[]' type. -tests/cases/compiler/controlFlowArrayErrors.ts(14,9): error TS7005: Variable 'y' implicitly has an 'any[]' type. +tests/cases/compiler/controlFlowArrayErrors.ts(5,9): error TS7034: Variable 'x' implicitly has type 'any[]' in some locations where its type cannot be determined. +tests/cases/compiler/controlFlowArrayErrors.ts(6,13): error TS7005: Variable 'x' implicitly has an 'any[]' type. +tests/cases/compiler/controlFlowArrayErrors.ts(12,9): error TS7034: Variable 'x' implicitly has type 'any[]' in some locations where its type cannot be determined. +tests/cases/compiler/controlFlowArrayErrors.ts(14,13): error TS7005: Variable 'x' implicitly has an 'any[]' type. tests/cases/compiler/controlFlowArrayErrors.ts(20,9): error TS7034: Variable 'x' implicitly has type 'any[]' in some locations where its type cannot be determined. tests/cases/compiler/controlFlowArrayErrors.ts(23,9): error TS7005: Variable 'x' implicitly has an 'any[]' type. tests/cases/compiler/controlFlowArrayErrors.ts(30,12): error TS2345: Argument of type 'true' is not assignable to parameter of type 'string | number'. @@ -10,25 +12,29 @@ tests/cases/compiler/controlFlowArrayErrors.ts(61,11): error TS7034: Variable 'x tests/cases/compiler/controlFlowArrayErrors.ts(64,9): error TS7005: Variable 'x' implicitly has an 'any[]' type. -==== tests/cases/compiler/controlFlowArrayErrors.ts (10 errors) ==== +==== tests/cases/compiler/controlFlowArrayErrors.ts (12 errors) ==== declare function cond(): boolean; function f1() { - let x = []; - let y = x; // Implicit any[] error + let x = []; // Implicit any[] error in some locations ~ -!!! error TS7005: Variable 'y' implicitly has an 'any[]' type. +!!! error TS7034: Variable 'x' implicitly has type 'any[]' in some locations where its type cannot be determined. + let y = x; // Implicit any[] error + ~ +!!! error TS7005: Variable 'x' implicitly has an 'any[]' type. x.push(5); let z = x; } function f2() { - let x; - x = []; - let y = x; // Implicit any[] error + let x; // Implicit any[] error in some locations ~ -!!! error TS7005: Variable 'y' implicitly has an 'any[]' type. +!!! error TS7034: Variable 'x' implicitly has type 'any[]' in some locations where its type cannot be determined. + x = []; + let y = x; // Implicit any[] error + ~ +!!! error TS7005: Variable 'x' implicitly has an 'any[]' type. x.push(5); let z = x; } @@ -39,7 +45,7 @@ tests/cases/compiler/controlFlowArrayErrors.ts(64,9): error TS7005: Variable 'x' !!! error TS7034: Variable 'x' implicitly has type 'any[]' in some locations where its type cannot be determined. x.push(5); function g() { - x; // Implicit any[] error + x; // Implicit any[] error ~ !!! error TS7005: Variable 'x' implicitly has an 'any[]' type. } diff --git a/tests/baselines/reference/controlFlowArrayErrors.js b/tests/baselines/reference/controlFlowArrayErrors.js index 538addc31c029..59995eb309475 100644 --- a/tests/baselines/reference/controlFlowArrayErrors.js +++ b/tests/baselines/reference/controlFlowArrayErrors.js @@ -3,16 +3,16 @@ declare function cond(): boolean; function f1() { - let x = []; - let y = x; // Implicit any[] error + let x = []; // Implicit any[] error in some locations + let y = x; // Implicit any[] error x.push(5); let z = x; } function f2() { - let x; + let x; // Implicit any[] error in some locations x = []; - let y = x; // Implicit any[] error + let y = x; // Implicit any[] error x.push(5); let z = x; } @@ -21,7 +21,7 @@ function f3() { let x = []; // Implicit any[] error in some locations x.push(5); function g() { - x; // Implicit any[] error + x; // Implicit any[] error } } @@ -68,13 +68,13 @@ function f8() { //// [controlFlowArrayErrors.js] function f1() { - var x = []; + var x = []; // Implicit any[] error in some locations var y = x; // Implicit any[] error x.push(5); var z = x; } function f2() { - var x; + var x; // Implicit any[] error in some locations x = []; var y = x; // Implicit any[] error x.push(5); diff --git a/tests/baselines/reference/controlFlowArrays.js b/tests/baselines/reference/controlFlowArrays.js index b2d3fa8a2f232..3f1195994952b 100644 --- a/tests/baselines/reference/controlFlowArrays.js +++ b/tests/baselines/reference/controlFlowArrays.js @@ -114,13 +114,19 @@ function f10() { function f11() { let x = []; - return x; // never[] + if (x.length === 0) { // x.length ok on implicit any[] + x.push("hello"); + } + return x; } function f12() { let x; x = []; - return x; // never[] + if (x.length === 0) { // x.length ok on implicit any[] + x.push("hello"); + } + return x; } function f13() { @@ -278,12 +284,18 @@ function f10() { } function f11() { var x = []; - return x; // never[] + if (x.length === 0) { + x.push("hello"); + } + return x; } function f12() { var x; x = []; - return x; // never[] + if (x.length === 0) { + x.push("hello"); + } + return x; } function f13() { var x = []; diff --git a/tests/baselines/reference/controlFlowArrays.symbols b/tests/baselines/reference/controlFlowArrays.symbols index 463fb5225857d..184a813a05fd2 100644 --- a/tests/baselines/reference/controlFlowArrays.symbols +++ b/tests/baselines/reference/controlFlowArrays.symbols @@ -282,78 +282,98 @@ function f11() { let x = []; >x : Symbol(x, Decl(controlFlowArrays.ts, 114, 7)) - return x; // never[] + if (x.length === 0) { // x.length ok on implicit any[] +>x.length : Symbol(Array.length, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 114, 7)) +>length : Symbol(Array.length, Decl(lib.d.ts, --, --)) + + x.push("hello"); +>x.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 114, 7)) +>push : Symbol(Array.push, Decl(lib.d.ts, --, --)) + } + return x; >x : Symbol(x, Decl(controlFlowArrays.ts, 114, 7)) } function f12() { ->f12 : Symbol(f12, Decl(controlFlowArrays.ts, 116, 1)) +>f12 : Symbol(f12, Decl(controlFlowArrays.ts, 119, 1)) let x; ->x : Symbol(x, Decl(controlFlowArrays.ts, 119, 7)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 122, 7)) x = []; ->x : Symbol(x, Decl(controlFlowArrays.ts, 119, 7)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 122, 7)) + + if (x.length === 0) { // x.length ok on implicit any[] +>x.length : Symbol(Array.length, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 122, 7)) +>length : Symbol(Array.length, Decl(lib.d.ts, --, --)) - return x; // never[] ->x : Symbol(x, Decl(controlFlowArrays.ts, 119, 7)) + x.push("hello"); +>x.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 122, 7)) +>push : Symbol(Array.push, Decl(lib.d.ts, --, --)) + } + return x; +>x : Symbol(x, Decl(controlFlowArrays.ts, 122, 7)) } function f13() { ->f13 : Symbol(f13, Decl(controlFlowArrays.ts, 122, 1)) +>f13 : Symbol(f13, Decl(controlFlowArrays.ts, 128, 1)) var x = []; ->x : Symbol(x, Decl(controlFlowArrays.ts, 125, 7)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 131, 7)) x.push(5); >x.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) ->x : Symbol(x, Decl(controlFlowArrays.ts, 125, 7)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 131, 7)) >push : Symbol(Array.push, Decl(lib.d.ts, --, --)) x.push("hello"); >x.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) ->x : Symbol(x, Decl(controlFlowArrays.ts, 125, 7)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 131, 7)) >push : Symbol(Array.push, Decl(lib.d.ts, --, --)) x.push(true); >x.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) ->x : Symbol(x, Decl(controlFlowArrays.ts, 125, 7)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 131, 7)) >push : Symbol(Array.push, Decl(lib.d.ts, --, --)) return x; // (string | number | boolean)[] ->x : Symbol(x, Decl(controlFlowArrays.ts, 125, 7)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 131, 7)) } function f14() { ->f14 : Symbol(f14, Decl(controlFlowArrays.ts, 130, 1)) +>f14 : Symbol(f14, Decl(controlFlowArrays.ts, 136, 1)) const x = []; ->x : Symbol(x, Decl(controlFlowArrays.ts, 133, 9)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 139, 9)) x.push(5); >x.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) ->x : Symbol(x, Decl(controlFlowArrays.ts, 133, 9)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 139, 9)) >push : Symbol(Array.push, Decl(lib.d.ts, --, --)) x.push("hello"); >x.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) ->x : Symbol(x, Decl(controlFlowArrays.ts, 133, 9)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 139, 9)) >push : Symbol(Array.push, Decl(lib.d.ts, --, --)) x.push(true); >x.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) ->x : Symbol(x, Decl(controlFlowArrays.ts, 133, 9)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 139, 9)) >push : Symbol(Array.push, Decl(lib.d.ts, --, --)) return x; // (string | number | boolean)[] ->x : Symbol(x, Decl(controlFlowArrays.ts, 133, 9)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 139, 9)) } function f15() { ->f15 : Symbol(f15, Decl(controlFlowArrays.ts, 138, 1)) +>f15 : Symbol(f15, Decl(controlFlowArrays.ts, 144, 1)) let x = []; ->x : Symbol(x, Decl(controlFlowArrays.ts, 141, 7)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 147, 7)) while (cond()) { >cond : Symbol(cond, Decl(controlFlowArrays.ts, 0, 0)) @@ -363,88 +383,88 @@ function f15() { x.push("hello"); >x.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) ->x : Symbol(x, Decl(controlFlowArrays.ts, 141, 7)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 147, 7)) >push : Symbol(Array.push, Decl(lib.d.ts, --, --)) } return x; // string[] ->x : Symbol(x, Decl(controlFlowArrays.ts, 141, 7)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 147, 7)) } function f16() { ->f16 : Symbol(f16, Decl(controlFlowArrays.ts, 147, 1)) +>f16 : Symbol(f16, Decl(controlFlowArrays.ts, 153, 1)) let x; ->x : Symbol(x, Decl(controlFlowArrays.ts, 150, 7)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 156, 7)) let y; ->y : Symbol(y, Decl(controlFlowArrays.ts, 151, 7)) +>y : Symbol(y, Decl(controlFlowArrays.ts, 157, 7)) (x = [], x).push(5); >(x = [], x).push : Symbol(Array.push, Decl(lib.d.ts, --, --)) ->x : Symbol(x, Decl(controlFlowArrays.ts, 150, 7)) ->x : Symbol(x, Decl(controlFlowArrays.ts, 150, 7)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 156, 7)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 156, 7)) >push : Symbol(Array.push, Decl(lib.d.ts, --, --)) (x.push("hello"), x).push(true); >(x.push("hello"), x).push : Symbol(Array.push, Decl(lib.d.ts, --, --)) >x.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) ->x : Symbol(x, Decl(controlFlowArrays.ts, 150, 7)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 156, 7)) >push : Symbol(Array.push, Decl(lib.d.ts, --, --)) ->x : Symbol(x, Decl(controlFlowArrays.ts, 150, 7)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 156, 7)) >push : Symbol(Array.push, Decl(lib.d.ts, --, --)) ((x))[3] = { a: 1 }; ->x : Symbol(x, Decl(controlFlowArrays.ts, 150, 7)) ->a : Symbol(a, Decl(controlFlowArrays.ts, 154, 16)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 156, 7)) +>a : Symbol(a, Decl(controlFlowArrays.ts, 160, 16)) return x; // (string | number | boolean | { a: number })[] ->x : Symbol(x, Decl(controlFlowArrays.ts, 150, 7)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 156, 7)) } function f17() { ->f17 : Symbol(f17, Decl(controlFlowArrays.ts, 156, 1)) +>f17 : Symbol(f17, Decl(controlFlowArrays.ts, 162, 1)) let x = []; ->x : Symbol(x, Decl(controlFlowArrays.ts, 159, 7)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 165, 7)) x.unshift(5); >x.unshift : Symbol(Array.unshift, Decl(lib.d.ts, --, --)) ->x : Symbol(x, Decl(controlFlowArrays.ts, 159, 7)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 165, 7)) >unshift : Symbol(Array.unshift, Decl(lib.d.ts, --, --)) x.unshift("hello"); >x.unshift : Symbol(Array.unshift, Decl(lib.d.ts, --, --)) ->x : Symbol(x, Decl(controlFlowArrays.ts, 159, 7)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 165, 7)) >unshift : Symbol(Array.unshift, Decl(lib.d.ts, --, --)) x.unshift(true); >x.unshift : Symbol(Array.unshift, Decl(lib.d.ts, --, --)) ->x : Symbol(x, Decl(controlFlowArrays.ts, 159, 7)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 165, 7)) >unshift : Symbol(Array.unshift, Decl(lib.d.ts, --, --)) return x; // (string | number | boolean)[] ->x : Symbol(x, Decl(controlFlowArrays.ts, 159, 7)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 165, 7)) } function f18() { ->f18 : Symbol(f18, Decl(controlFlowArrays.ts, 164, 1)) +>f18 : Symbol(f18, Decl(controlFlowArrays.ts, 170, 1)) let x = []; ->x : Symbol(x, Decl(controlFlowArrays.ts, 167, 7)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 173, 7)) x.push(5); >x.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) ->x : Symbol(x, Decl(controlFlowArrays.ts, 167, 7)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 173, 7)) >push : Symbol(Array.push, Decl(lib.d.ts, --, --)) x.unshift("hello"); >x.unshift : Symbol(Array.unshift, Decl(lib.d.ts, --, --)) ->x : Symbol(x, Decl(controlFlowArrays.ts, 167, 7)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 173, 7)) >unshift : Symbol(Array.unshift, Decl(lib.d.ts, --, --)) x[2] = true; ->x : Symbol(x, Decl(controlFlowArrays.ts, 167, 7)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 173, 7)) return x; // (string | number | boolean)[] ->x : Symbol(x, Decl(controlFlowArrays.ts, 167, 7)) +>x : Symbol(x, Decl(controlFlowArrays.ts, 173, 7)) } diff --git a/tests/baselines/reference/controlFlowArrays.types b/tests/baselines/reference/controlFlowArrays.types index 2299afd7d21cd..2a27bbf10ded4 100644 --- a/tests/baselines/reference/controlFlowArrays.types +++ b/tests/baselines/reference/controlFlowArrays.types @@ -357,18 +357,32 @@ function f10() { } function f11() { ->f11 : () => never[] +>f11 : () => string[] let x = []; >x : any[] >[] : never[] - return x; // never[] ->x : never[] + if (x.length === 0) { // x.length ok on implicit any[] +>x.length === 0 : boolean +>x.length : number +>x : any[] +>length : number +>0 : 0 + + x.push("hello"); +>x.push("hello") : number +>x.push : (...items: any[]) => number +>x : any[] +>push : (...items: any[]) => number +>"hello" : "hello" + } + return x; +>x : string[] } function f12() { ->f12 : () => never[] +>f12 : () => string[] let x; >x : any @@ -378,8 +392,22 @@ function f12() { >x : any >[] : never[] - return x; // never[] ->x : never[] + if (x.length === 0) { // x.length ok on implicit any[] +>x.length === 0 : boolean +>x.length : number +>x : any[] +>length : number +>0 : 0 + + x.push("hello"); +>x.push("hello") : number +>x.push : (...items: any[]) => number +>x : any[] +>push : (...items: any[]) => number +>"hello" : "hello" + } + return x; +>x : string[] } function f13() { diff --git a/tests/baselines/reference/genericTypeParameterEquivalence2.types b/tests/baselines/reference/genericTypeParameterEquivalence2.types index 512537645781d..6ab297fe32cfd 100644 --- a/tests/baselines/reference/genericTypeParameterEquivalence2.types +++ b/tests/baselines/reference/genericTypeParameterEquivalence2.types @@ -105,7 +105,7 @@ function filter(f: (a: A) => boolean, ar: A[]): A[] { } ); return ret; ->ret : undefined[] +>ret : any[] } // length :: [a] -> Num diff --git a/tests/baselines/reference/globalThisCapture.types b/tests/baselines/reference/globalThisCapture.types index d17c899d1ed88..063100ff78eaf 100644 --- a/tests/baselines/reference/globalThisCapture.types +++ b/tests/baselines/reference/globalThisCapture.types @@ -13,7 +13,7 @@ var parts = []; // Ensure that the generated code is correct parts[0]; ->parts[0] : undefined ->parts : undefined[] +>parts[0] : any +>parts : any[] >0 : 0