diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index b9e26e4f38dce..f333afe1cef1c 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -785,6 +785,15 @@ namespace ts { }; } + function createFlowArrayMutation(antecedent: FlowNode, node: CallExpression | BinaryExpression): FlowNode { + setFlowNodeReferenced(antecedent); + return { + flags: FlowFlags.ArrayMutation, + antecedent, + node + }; + } + function finishFlowLabel(flow: FlowLabel): FlowNode { const antecedents = flow.antecedents; if (!antecedents) { @@ -1165,6 +1174,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 (isNarrowableOperand(elementAccess.expression)) { + currentFlow = createFlowArrayMutation(currentFlow, node); + } + } } } } @@ -1225,6 +1240,12 @@ namespace ts { else { forEachChild(node, bind); } + if (node.expression.kind === SyntaxKind.PropertyAccessExpression) { + const propertyAccess = node.expression; + if (isNarrowableOperand(propertyAccess.expression) && isPushOrUnshiftIdentifier(propertyAccess.name)) { + currentFlow = createFlowArrayMutation(currentFlow, node); + } + } } function getContainerFlags(node: Node): ContainerFlags { diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4033267456775..4c1fb965ed9be 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -115,6 +115,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__"); @@ -176,6 +177,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. @@ -3061,9 +3063,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 { @@ -3104,12 +3111,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) { @@ -7423,7 +7436,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; } @@ -7432,7 +7445,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; } @@ -7571,10 +7584,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; @@ -7992,7 +8005,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; @@ -8395,6 +8408,13 @@ 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: @@ -8410,6 +8430,14 @@ namespace ts { return 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 ? + getReferenceRoot(parent) : node; + } + function getTypeOfSwitchClause(clause: CaseClause | DefaultClause) { if (clause.kind === SyntaxKind.CaseClause) { const caseType = getRegularTypeOfLiteralType(checkExpression((clause).expression)); @@ -8495,21 +8523,113 @@ 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); + } + + function createFinalArrayType(elementType: Type) { + 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. + function getFinalArrayType(evolvingArrayType: AnonymousType): Type { + return evolvingArrayType.finalArrayType || (evolvingArrayType.finalArrayType = createFinalArrayType(evolvingArrayType.elementType)); + } + + function finalizeEvolvingArrayType(type: Type): Type { + return isEvolvingArrayType(type) ? getFinalArrayType(type) : type; + } + + 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 isEvolvingArrayTypeList(types) ? + getEvolvingArrayType(getUnionType(map(types, getElementTypeOfEvolvingArrayType))) : + getUnionType(sameMap(types, finalizeEvolvingArrayType), subtypeReduction); + } + + // 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; + 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) { 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.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; } - return result; + return resultType; function getTypeAtFlowNode(flow: FlowNode): FlowType { while (true) { @@ -8546,6 +8666,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; @@ -8558,8 +8685,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. @@ -8580,9 +8707,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 @@ -8595,24 +8730,56 @@ 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; + 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)) { + let evolvedType = type; + if (node.kind === SyntaxKind.CallExpression) { + for (const arg of (node).arguments) { + evolvedType = addEvolvingArrayElementType(evolvedType, arg); + } + } + 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)); } + 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 { @@ -8655,7 +8822,7 @@ namespace ts { seenIncomplete = true; } } - return createFlowType(getUnionType(antecedentTypes, subtypeReduction), seenIncomplete); + return createFlowType(getUnionOrEvolvingArrayType(antecedentTypes, subtypeReduction), seenIncomplete); } function getTypeAtFlowLoopLabel(flow: FlowLabel): FlowType { @@ -8671,11 +8838,15 @@ 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) { - return createFlowType(getUnionType(flowLoopTypes[i]), /*incomplete*/ true); + if (flowLoopNodes[i] === flow && flowLoopKeys[i] === key && flowLoopTypes[i].length) { + return createFlowType(getUnionOrEvolvingArrayType(flowLoopTypes[i], /*subtypeReduction*/ false), /*incomplete*/ true); } } // Add the flow loop junction and reference to the in-process stack and analyze @@ -8718,7 +8889,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); } @@ -9100,6 +9271,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); @@ -9190,30 +9365,28 @@ namespace ts { // When the control flow originates in a function expression or arrow function and we are referencing // a const variable or parameter from an outer function, we extend the origin of the control flow // analysis to include the immediately enclosing function. - while (flowContainer !== declarationContainer && - (flowContainer.kind === SyntaxKind.FunctionExpression || - flowContainer.kind === SyntaxKind.ArrowFunction || - isObjectLiteralOrClassExpressionMethod(flowContainer)) && - (isReadonlySymbol(localOrExportSymbol) || isParameter && !isParameterAssigned(localOrExportSymbol))) { + while (flowContainer !== declarationContainer && (flowContainer.kind === SyntaxKind.FunctionExpression || + flowContainer.kind === SyntaxKind.ArrowFunction || isObjectLiteralOrClassExpressionMethod(flowContainer)) && + (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 // 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 === autoType || flowType === autoArrayType) { 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(flowType)); + error(node, Diagnostics.Variable_0_implicitly_has_an_1_type, symbolToString(symbol), typeToString(flowType)); } - return anyType; + return convertAutoToAny(flowType); } } else if (!assumeInitialized && !(getFalsyFlags(type) & TypeFlags.Undefined) && getFalsyFlags(flowType) & TypeFlags.Undefined) { @@ -10657,7 +10830,7 @@ namespace ts { } } - return getUnionType(signatures.map(getReturnTypeOfSignature), /*subtypeReduction*/ true); + return getUnionType(map(signatures, getReturnTypeOfSignature), /*subtypeReduction*/ true); } /// e.g. "props" for React.d.ts, @@ -10707,7 +10880,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); } @@ -15966,7 +16139,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 @@ -19384,6 +19557,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/core.ts b/src/compiler/core.ts index 7c68306211a78..7c862cf59eff6 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -265,13 +265,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 if 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. * diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 815bb59cb0f00..21f04da488d9b 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -2961,7 +2961,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 e87fa3e786fa9..36b5303e6c41e 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1907,8 +1907,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 } @@ -1951,6 +1952,13 @@ namespace ts { antecedent: FlowNode; } + // FlowArrayMutation represents a node potentially mutates an array, i.e. an + // operation of the form 'x.push(value)', 'x.unshift(value)' or 'x[n] = value'. + export interface FlowArrayMutation extends FlowNode { + node: CallExpression | BinaryExpression; + antecedent: FlowNode; + } + export type FlowType = Type | IncompleteType; // Incomplete types occur during control flow analysis of loops. An IncompleteType @@ -2748,6 +2756,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 */ diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 498c195ea3ab7..e788bcca2aa6c 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1901,6 +1901,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: diff --git a/tests/baselines/reference/controlFlowArrayErrors.errors.txt b/tests/baselines/reference/controlFlowArrayErrors.errors.txt new file mode 100644 index 0000000000000..2ef009dc0e115 --- /dev/null +++ b/tests/baselines/reference/controlFlowArrayErrors.errors.txt @@ -0,0 +1,105 @@ +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'. +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 (12 errors) ==== + + declare function cond(): boolean; + + function f1() { + 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. + 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; // Implicit any[] error in some locations + ~ +!!! 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; + } + + 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'. + } + + 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 new file mode 100644 index 0000000000000..59995eb309475 --- /dev/null +++ b/tests/baselines/reference/controlFlowArrayErrors.js @@ -0,0 +1,125 @@ +//// [controlFlowArrayErrors.ts] + +declare function cond(): boolean; + +function f1() { + let x = []; // Implicit any[] error in some locations + let y = x; // Implicit any[] error + x.push(5); + let z = x; +} + +function f2() { + let x; // Implicit any[] error in some locations + 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 +} + +function f8() { + const x = []; // Implicit any[] error in some locations + x.push(5); + function g() { + x; // Implicit any[] error + } +} + +//// [controlFlowArrayErrors.js] +function f1() { + var x = []; // Implicit any[] error in some locations + var y = x; // Implicit any[] error + x.push(5); + var z = x; +} +function f2() { + var x; // Implicit any[] error in some locations + 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 +} +function f8() { + var x = []; // Implicit any[] error in some locations + x.push(5); + function g() { + x; // Implicit any[] error + } +} diff --git a/tests/baselines/reference/controlFlowArrays.js b/tests/baselines/reference/controlFlowArrays.js new file mode 100644 index 0000000000000..3f1195994952b --- /dev/null +++ b/tests/baselines/reference/controlFlowArrays.js @@ -0,0 +1,343 @@ +//// [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 = []; + if (x.length === 0) { // x.length ok on implicit any[] + x.push("hello"); + } + return x; +} + +function f12() { + let x; + x = []; + if (x.length === 0) { // x.length ok on implicit any[] + x.push("hello"); + } + return x; +} + +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)[] +} + +function f15() { + let x = []; + while (cond()) { + while (cond()) {} + 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 })[] +} + +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] +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 = []; + if (x.length === 0) { + x.push("hello"); + } + return x; +} +function f12() { + var x; + x = []; + if (x.length === 0) { + x.push("hello"); + } + return x; +} +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)[] +} +function f15() { + var x = []; + while (cond()) { + while (cond()) { } + x.push("hello"); + } + 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 })[] +} +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 new file mode 100644 index 0000000000000..184a813a05fd2 --- /dev/null +++ b/tests/baselines/reference/controlFlowArrays.symbols @@ -0,0 +1,470 @@ +=== 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)) + + 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, 119, 1)) + + let x; +>x : Symbol(x, Decl(controlFlowArrays.ts, 122, 7)) + + x = []; +>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, --, --)) + + 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, 128, 1)) + + var x = []; +>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, 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, 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, 131, 7)) +>push : Symbol(Array.push, Decl(lib.d.ts, --, --)) + + return x; // (string | number | boolean)[] +>x : Symbol(x, Decl(controlFlowArrays.ts, 131, 7)) +} + +function f14() { +>f14 : Symbol(f14, Decl(controlFlowArrays.ts, 136, 1)) + + const x = []; +>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, 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, 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, 139, 9)) +>push : Symbol(Array.push, Decl(lib.d.ts, --, --)) + + return x; // (string | number | boolean)[] +>x : Symbol(x, Decl(controlFlowArrays.ts, 139, 9)) +} + +function f15() { +>f15 : Symbol(f15, Decl(controlFlowArrays.ts, 144, 1)) + + let x = []; +>x : Symbol(x, Decl(controlFlowArrays.ts, 147, 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, 147, 7)) +>push : Symbol(Array.push, Decl(lib.d.ts, --, --)) + } + return x; // string[] +>x : Symbol(x, Decl(controlFlowArrays.ts, 147, 7)) +} + +function f16() { +>f16 : Symbol(f16, Decl(controlFlowArrays.ts, 153, 1)) + + let x; +>x : Symbol(x, Decl(controlFlowArrays.ts, 156, 7)) + + let y; +>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, 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, 156, 7)) +>push : Symbol(Array.push, Decl(lib.d.ts, --, --)) +>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, 156, 7)) +>a : Symbol(a, Decl(controlFlowArrays.ts, 160, 16)) + + return x; // (string | number | boolean | { a: number })[] +>x : Symbol(x, Decl(controlFlowArrays.ts, 156, 7)) +} + +function f17() { +>f17 : Symbol(f17, Decl(controlFlowArrays.ts, 162, 1)) + + let x = []; +>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, 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, 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, 165, 7)) +>unshift : Symbol(Array.unshift, Decl(lib.d.ts, --, --)) + + return x; // (string | number | boolean)[] +>x : Symbol(x, Decl(controlFlowArrays.ts, 165, 7)) +} + +function f18() { +>f18 : Symbol(f18, Decl(controlFlowArrays.ts, 170, 1)) + + let x = []; +>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, 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, 173, 7)) +>unshift : Symbol(Array.unshift, Decl(lib.d.ts, --, --)) + + x[2] = true; +>x : Symbol(x, Decl(controlFlowArrays.ts, 173, 7)) + + return x; // (string | number | boolean)[] +>x : Symbol(x, Decl(controlFlowArrays.ts, 173, 7)) +} diff --git a/tests/baselines/reference/controlFlowArrays.types b/tests/baselines/reference/controlFlowArrays.types new file mode 100644 index 0000000000000..2a27bbf10ded4 --- /dev/null +++ b/tests/baselines/reference/controlFlowArrays.types @@ -0,0 +1,615 @@ +=== 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 : () => string[] + + let x = []; +>x : any[] +>[] : 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 : () => string[] + + let x; +>x : any + + x = []; +>x = [] : never[] +>x : any +>[] : 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() { +>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)[] +} + +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[] +} + +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; })[] +} + +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/baselines/reference/implicitAnyWidenToAny.errors.txt b/tests/baselines/reference/implicitAnyWidenToAny.errors.txt index 95739a450a888..4302211d9a0eb 100644 --- a/tests/baselines/reference/implicitAnyWidenToAny.errors.txt +++ b/tests/baselines/reference/implicitAnyWidenToAny.errors.txt @@ -1,17 +1,14 @@ 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" var widenArray = [null, undefined]; // error at "widenArray" ~~~~~~~~~~ !!! error TS7005: Variable 'widenArray' implicitly has an 'any[]' type. - var emptyArray = []; // error at "emptyArray" - ~~~~~~~~~~ -!!! error TS7005: Variable 'emptyArray' implicitly has an 'any[]' type. + 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/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)[] } diff --git a/tests/cases/compiler/controlFlowArrayErrors.ts b/tests/cases/compiler/controlFlowArrayErrors.ts new file mode 100644 index 0000000000000..08107ff365994 --- /dev/null +++ b/tests/cases/compiler/controlFlowArrayErrors.ts @@ -0,0 +1,67 @@ +// @noImplicitAny: true + +declare function cond(): boolean; + +function f1() { + let x = []; // Implicit any[] error in some locations + let y = x; // Implicit any[] error + x.push(5); + let z = x; +} + +function f2() { + let x; // Implicit any[] error in some locations + 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 +} + +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 diff --git a/tests/cases/compiler/controlFlowArrays.ts b/tests/cases/compiler/controlFlowArrays.ts new file mode 100644 index 0000000000000..646c85f069f89 --- /dev/null +++ b/tests/cases/compiler/controlFlowArrays.ts @@ -0,0 +1,181 @@ +// @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 = []; + if (x.length === 0) { // x.length ok on implicit any[] + x.push("hello"); + } + return x; +} + +function f12() { + let x; + x = []; + if (x.length === 0) { // x.length ok on implicit any[] + x.push("hello"); + } + return x; +} + +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)[] +} + +function f15() { + let x = []; + while (cond()) { + while (cond()) {} + 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 })[] +} + +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 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 {