From c85ba7b73b9bf2a19d8371df46fdb45f6ce027cc Mon Sep 17 00:00:00 2001 From: Tycho Grouwstra Date: Sun, 13 Aug 2017 15:00:58 +0800 Subject: [PATCH 1/2] add widenTypes flag --- doc/spec.md | 10 +- src/compiler/checker.ts | 154 +++++++++--------- src/compiler/commandLineParser.ts | 7 + src/compiler/diagnosticMessages.json | 4 + src/compiler/types.ts | 1 + .../unittests/configurationExtension.ts | 6 + src/harness/unittests/transpile.ts | 4 + src/server/protocol.ts | 1 + tests/baselines/reference/dontWiden.js | 41 +++++ tests/baselines/reference/dontWiden.symbols | 79 +++++++++ tests/baselines/reference/dontWiden.types | 120 ++++++++++++++ .../Supports setting granularConst.js | 2 + .../transpile/Supports setting widenTypes.js | 2 + .../tsconfig.json | 1 + .../tsconfig.json | 1 + .../tsconfig.json | 1 + .../tsconfig.json | 1 + .../tsconfig.json | 1 + .../tsconfig.json | 1 + .../tsconfig.json | 1 + .../tsconfig.json | 1 + tests/cases/compiler/dontWiden.ts | 25 +++ 22 files changed, 385 insertions(+), 79 deletions(-) create mode 100644 tests/baselines/reference/dontWiden.js create mode 100644 tests/baselines/reference/dontWiden.symbols create mode 100644 tests/baselines/reference/dontWiden.types create mode 100644 tests/baselines/reference/transpile/Supports setting granularConst.js create mode 100644 tests/baselines/reference/transpile/Supports setting widenTypes.js create mode 100644 tests/cases/compiler/dontWiden.ts diff --git a/doc/spec.md b/doc/spec.md index fa69d32105663..5c9ccdc0fdeb2 100644 --- a/doc/spec.md +++ b/doc/spec.md @@ -265,7 +265,7 @@ function f() { To benefit from this inference, a programmer can use the TypeScript language service. For example, a code editor can incorporate the TypeScript language service and use the service to find the members of a string object as in the following screen shot. -  ![](images/image1.png) +  ![](images/image1.png) In this example, the programmer benefits from type inference without providing type annotations. Some beneficial tools, however, do require the programmer to provide type annotations. In TypeScript, we can express a parameter requirement as in the following code fragment. @@ -413,7 +413,7 @@ This signature denotes that a function may be passed as the parameter of the '$' A typical client would not need to add any additional typing but could just use a community-supplied typing to discover (through statement completion with documentation tips) and verify (through static checking) correct use of the library, as in the following screen shot. -  ![](images/image2.png) +  ![](images/image2.png) Section [3.3](#3.3) provides additional information about object types. @@ -630,7 +630,7 @@ An important goal of TypeScript is to provide accurate and straightforward types JavaScript programming interfaces often include functions whose behavior is discriminated by a string constant passed to the function. The Document Object Model makes heavy use of this pattern. For example, the following screen shot shows that the 'createElement' method of the 'document' object has multiple signatures, some of which identify the types returned when specific strings are passed into the method. -  ![](images/image3.png) +  ![](images/image3.png) The following code fragment uses this feature. Because the 'span' variable is inferred to have the type 'HTMLSpanElement', the code can reference without static error the 'isMultiline' property of 'span'. @@ -641,7 +641,7 @@ span.isMultiLine = false; // OK: HTMLSpanElement has isMultiline property In the following screen shot, a programming tool combines information from overloading on string parameters with contextual typing to infer that the type of the variable 'e' is 'MouseEvent' and that therefore 'e' has a 'clientX' property. -  ![](images/image4.png) +  ![](images/image4.png) Section [3.9.2.4](#3.9.2.4) provides details on how to use string literals in function signatures. @@ -2630,7 +2630,7 @@ Each element expression in a non-empty array literal is processed as follows: The resulting type an array literal expression is determined as follows: -* If the array literal is empty, the resulting type is an array type with the element type Undefined. +* If the array literal is empty, the resulting type is an empty tuple type in `granularConst` mode, otherwise an array type with the element type Undefined. * Otherwise, if the array literal contains no spread elements and is contextually typed by a tuple-like type (section [3.3.3](#3.3.3)), the resulting type is a tuple type constructed from the types of the element expressions. * Otherwise, if the array literal contains no spread elements and is an array assignment pattern in a destructuring assignment (section [4.21.1](#4.21.1)), the resulting type is a tuple type constructed from the types of the element expressions. * Otherwise, the resulting type is an array type with an element type that is the union of the types of the non-spread element expressions and the numeric index signature types of the spread element expressions. diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 903287707503c..89161c9ab42c3 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -64,6 +64,8 @@ namespace ts { const noUnusedIdentifiers = !!compilerOptions.noUnusedLocals || !!compilerOptions.noUnusedParameters; const allowSyntheticDefaultImports = typeof compilerOptions.allowSyntheticDefaultImports !== "undefined" ? compilerOptions.allowSyntheticDefaultImports : modulekind === ModuleKind.System; const strictNullChecks = compilerOptions.strictNullChecks === undefined ? compilerOptions.strict : compilerOptions.strictNullChecks; + const widenTypes = compilerOptions.widenTypes === undefined ? true : compilerOptions.widenTypes; + const granularConst = !widenTypes; const noImplicitAny = compilerOptions.noImplicitAny === undefined ? compilerOptions.strict : compilerOptions.noImplicitAny; const noImplicitThis = compilerOptions.noImplicitThis === undefined ? compilerOptions.strict : compilerOptions.noImplicitThis; @@ -13302,12 +13304,12 @@ namespace ts { return result; } - function checkSpreadExpression(node: SpreadElement, checkMode?: CheckMode): Type { + function checkSpreadExpression(node: SpreadElement, checkMode?: CheckMode, granular?: boolean): Type { if (languageVersion < ScriptTarget.ES2015 && compilerOptions.downlevelIteration) { checkExternalEmitHelpers(node, ExternalEmitHelpers.SpreadIncludes); } - const arrayOrIterableType = checkExpression(node.expression, checkMode); + const arrayOrIterableType = checkExpression(node.expression, checkMode, granular); return checkIteratedTypeOrElementType(arrayOrIterableType, node.expression, /*allowStringInput*/ false, /*allowAsyncIterables*/ false); } @@ -13316,7 +13318,7 @@ namespace ts { (node.kind === SyntaxKind.BinaryExpression && (node).operatorToken.kind === SyntaxKind.EqualsToken); } - function checkArrayLiteral(node: ArrayLiteralExpression, checkMode?: CheckMode): Type { + function checkArrayLiteral(node: ArrayLiteralExpression, checkMode?: CheckMode, granular?: boolean): Type { const elements = node.elements; let hasSpreadElement = false; const elementTypes: Type[] = []; @@ -13335,7 +13337,7 @@ namespace ts { // get the contextual element type from it. So we do something similar to // getContextualTypeForElementExpression, which will crucially not error // if there is no index type / iterated type. - const restArrayType = checkExpression((e).expression, checkMode); + const restArrayType = checkExpression((e).expression, checkMode, granular); const restElementType = getIndexTypeOfType(restArrayType, IndexKind.Number) || getIteratedTypeOrElementType(restArrayType, /*errorNode*/ undefined, /*allowStringInput*/ false, /*allowAsyncIterables*/ false, /*checkAssignability*/ false); if (restElementType) { @@ -13343,7 +13345,7 @@ namespace ts { } } else { - const type = checkExpressionForMutableLocation(e, checkMode); + const type = checkExpressionForMutableLocation(e, checkMode, granular); elementTypes.push(type); } hasSpreadElement = hasSpreadElement || e.kind === SyntaxKind.SpreadElement; @@ -13381,9 +13383,10 @@ namespace ts { } } } - return createArrayType(elementTypes.length ? - getUnionType(elementTypes, /*subtypeReduction*/ true) : - strictNullChecks ? neverType : undefinedWideningType); + return granular ? createTupleType(elementTypes) : + createArrayType(elementTypes.length ? + getUnionType(elementTypes, /*subtypeReduction*/ true) : + strictNullChecks ? neverType : undefinedWideningType); } function isNumericName(name: DeclarationName): boolean { @@ -13465,7 +13468,7 @@ namespace ts { return createIndexInfo(unionType, /*isReadonly*/ false); } - function checkObjectLiteral(node: ObjectLiteralExpression, checkMode?: CheckMode): Type { + function checkObjectLiteral(node: ObjectLiteralExpression, checkMode?: CheckMode, granular?: boolean): Type { const inDestructuringPattern = isAssignmentTarget(node); // Grammar checking checkGrammarObjectLiteralExpression(node, inDestructuringPattern); @@ -13499,14 +13502,14 @@ namespace ts { let type: Type; if (memberDecl.kind === SyntaxKind.PropertyAssignment) { - type = checkPropertyAssignment(memberDecl, checkMode); + type = checkPropertyAssignment(memberDecl, checkMode, granular); } else if (memberDecl.kind === SyntaxKind.MethodDeclaration) { - type = checkObjectLiteralMethod(memberDecl, checkMode); + type = checkObjectLiteralMethod(memberDecl, checkMode, granular); } else { Debug.assert(memberDecl.kind === SyntaxKind.ShorthandPropertyAssignment); - type = checkExpressionForMutableLocation((memberDecl).name, checkMode); + type = checkExpressionForMutableLocation((memberDecl).name, checkMode, granular); } if (jsdocType) { @@ -13707,7 +13710,7 @@ namespace ts { * @remarks Because this function calls getSpreadType, it needs to use the same checks as checkObjectLiteral, * which also calls getSpreadType. */ - function createJsxAttributesTypeFromAttributesProperty(openingLikeElement: JsxOpeningLikeElement, filter?: (symbol: Symbol) => boolean, checkMode?: CheckMode) { + function createJsxAttributesTypeFromAttributesProperty(openingLikeElement: JsxOpeningLikeElement, filter?: (symbol: Symbol) => boolean, checkMode?: CheckMode, granular?: boolean) { const attributes = openingLikeElement.attributes; let attributesTable = createSymbolTable(); let spread: Type = emptyObjectType; @@ -13721,7 +13724,7 @@ namespace ts { const member = attributeDecl.symbol; if (isJsxAttribute(attributeDecl)) { const exprType = attributeDecl.initializer ? - checkExpression(attributeDecl.initializer, checkMode) : + checkExpression(attributeDecl.initializer, checkMode, granular) : trueType; // is sugar for const attributeSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient | member.flags, member.escapedName); @@ -13835,8 +13838,8 @@ namespace ts { * (See "checkApplicableSignatureForJsxOpeningLikeElement" for how the function is used) * @param node a JSXAttributes to be resolved of its type */ - function checkJsxAttributes(node: JsxAttributes, checkMode?: CheckMode) { - return createJsxAttributesTypeFromAttributesProperty(node.parent as JsxOpeningLikeElement, /*filter*/ undefined, checkMode); + function checkJsxAttributes(node: JsxAttributes, checkMode?: CheckMode, granular?: boolean) { + return createJsxAttributesTypeFromAttributesProperty(node.parent as JsxOpeningLikeElement, /*filter*/ undefined, checkMode, granular); } function getJsxType(name: __String) { @@ -14440,9 +14443,9 @@ namespace ts { } } - function checkJsxExpression(node: JsxExpression, checkMode?: CheckMode) { + function checkJsxExpression(node: JsxExpression, checkMode?: CheckMode, granular?: boolean) { if (node.expression) { - const type = checkExpression(node.expression, checkMode); + const type = checkExpression(node.expression, checkMode, granular); if (node.dotDotDotToken && type !== anyType && !isArrayType(type)) { error(node, Diagnostics.JSX_spread_child_must_be_an_array_type, node.toString(), typeToString(type)); } @@ -16486,8 +16489,8 @@ namespace ts { return checkAssertionWorker(node, node.type, node.expression); } - function checkAssertionWorker(errNode: Node, type: TypeNode, expression: UnaryExpression | Expression, checkMode?: CheckMode) { - const exprType = getRegularTypeOfObjectLiteral(getBaseTypeOfLiteralType(checkExpression(expression, checkMode))); + function checkAssertionWorker(errNode: Node, type: TypeNode, expression: UnaryExpression | Expression, checkMode?: CheckMode, granular?: boolean) { + const exprType = getRegularTypeOfObjectLiteral(getBaseTypeOfLiteralType(checkExpression(expression, checkMode, granular))); checkSourceElement(type); const targetType = getTypeFromTypeNode(type); @@ -16643,7 +16646,7 @@ namespace ts { return promiseType; } - function getReturnTypeFromBody(func: FunctionLikeDeclaration, checkMode?: CheckMode): Type { + function getReturnTypeFromBody(func: FunctionLikeDeclaration, checkMode?: CheckMode, granular?: boolean): Type { const contextualSignature = getContextualSignatureForFunctionLikeDeclaration(func); if (!func.body) { return unknownType; @@ -16652,7 +16655,7 @@ namespace ts { const functionFlags = getFunctionFlags(func); let type: Type; if (func.body.kind !== SyntaxKind.Block) { - type = checkExpressionCached(func.body, checkMode); + type = checkExpressionCached(func.body, checkMode, granular); if (functionFlags & FunctionFlags.Async) { // From within an async function you can return either a non-promise value or a promise. Any // Promise/A+ compatible implementation will always assimilate any foreign promise, so the @@ -16664,7 +16667,7 @@ namespace ts { else { let types: Type[]; if (functionFlags & FunctionFlags.Generator) { // Generator or AsyncGenerator function - types = concatenate(checkAndAggregateYieldOperandTypes(func, checkMode), checkAndAggregateReturnExpressionTypes(func, checkMode)); + types = concatenate(checkAndAggregateYieldOperandTypes(func, checkMode, granular), checkAndAggregateReturnExpressionTypes(func, checkMode, granular)); if (!types || types.length === 0) { const iterableIteratorAny = functionFlags & FunctionFlags.Async ? createAsyncIterableIteratorType(anyType) // AsyncGenerator function @@ -16677,7 +16680,7 @@ namespace ts { } } else { - types = checkAndAggregateReturnExpressionTypes(func, checkMode); + types = checkAndAggregateReturnExpressionTypes(func, checkMode, granular); if (!types) { // For an async function, the return type will not be never, but rather a Promise for never. return functionFlags & FunctionFlags.Async @@ -16721,13 +16724,13 @@ namespace ts { : widenedType; // Generator function, AsyncGenerator function, or normal function } - function checkAndAggregateYieldOperandTypes(func: FunctionLikeDeclaration, checkMode: CheckMode): Type[] { + function checkAndAggregateYieldOperandTypes(func: FunctionLikeDeclaration, checkMode: CheckMode, granular?: boolean): Type[] { const aggregatedTypes: Type[] = []; const functionFlags = getFunctionFlags(func); forEachYieldExpression(func.body, yieldExpression => { const expr = yieldExpression.expression; if (expr) { - let type = checkExpressionCached(expr, checkMode); + let type = checkExpressionCached(expr, checkMode, granular); if (yieldExpression.asteriskToken) { // A yield* expression effectively yields everything that its operand yields type = checkIteratedTypeOrElementType(type, yieldExpression.expression, /*allowStringInput*/ false, (functionFlags & FunctionFlags.Async) !== 0); @@ -16772,7 +16775,7 @@ namespace ts { return true; } - function checkAndAggregateReturnExpressionTypes(func: FunctionLikeDeclaration, checkMode: CheckMode): Type[] { + function checkAndAggregateReturnExpressionTypes(func: FunctionLikeDeclaration, checkMode: CheckMode, granular?: boolean): Type[] { const functionFlags = getFunctionFlags(func); const aggregatedTypes: Type[] = []; let hasReturnWithNoExpression = functionHasImplicitReturn(func); @@ -16780,7 +16783,7 @@ namespace ts { forEachReturnStatement(func.body, returnStatement => { const expr = returnStatement.expression; if (expr) { - let type = checkExpressionCached(expr, checkMode); + let type = checkExpressionCached(expr, checkMode, granular); if (functionFlags & FunctionFlags.Async) { // From within an async function you can return either a non-promise value or a promise. Any // Promise/A+ compatible implementation will always assimilate any foreign promise, so the @@ -16867,7 +16870,7 @@ namespace ts { } } - function checkFunctionExpressionOrObjectLiteralMethod(node: FunctionExpression | MethodDeclaration, checkMode?: CheckMode): Type { + function checkFunctionExpressionOrObjectLiteralMethod(node: FunctionExpression | MethodDeclaration, checkMode?: CheckMode, granular?: boolean): Type { Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node)); // The identityMapper object is used to indicate that function expressions are wildcards @@ -16905,7 +16908,7 @@ namespace ts { assignContextualParameterTypes(signature, instantiatedContextualSignature); } if (!getEffectiveReturnTypeNode(node) && !signature.resolvedReturnType) { - const returnType = getReturnTypeFromBody(node, checkMode); + const returnType = getReturnTypeFromBody(node, checkMode, granular); if (!signature.resolvedReturnType) { signature.resolvedReturnType = returnType; } @@ -17276,7 +17279,7 @@ namespace ts { } } - function checkArrayLiteralAssignment(node: ArrayLiteralExpression, sourceType: Type, checkMode?: CheckMode): Type { + function checkArrayLiteralAssignment(node: ArrayLiteralExpression, sourceType: Type, checkMode?: CheckMode, granular?: boolean): Type { if (languageVersion < ScriptTarget.ES2015 && compilerOptions.downlevelIteration) { checkExternalEmitHelpers(node, ExternalEmitHelpers.Read); } @@ -17287,13 +17290,13 @@ namespace ts { const elementType = checkIteratedTypeOrElementType(sourceType, node, /*allowStringInput*/ false, /*allowAsyncIterables*/ false) || unknownType; const elements = node.elements; for (let i = 0; i < elements.length; i++) { - checkArrayLiteralDestructuringElementAssignment(node, sourceType, i, elementType, checkMode); + checkArrayLiteralDestructuringElementAssignment(node, sourceType, i, elementType, checkMode, granular); } return sourceType; } function checkArrayLiteralDestructuringElementAssignment(node: ArrayLiteralExpression, sourceType: Type, - elementIndex: number, elementType: Type, checkMode?: CheckMode) { + elementIndex: number, elementType: Type, checkMode?: CheckMode, granular?: boolean) { const elements = node.elements; const element = elements[elementIndex]; if (element.kind !== SyntaxKind.OmittedExpression) { @@ -17305,7 +17308,7 @@ namespace ts { ? getTypeOfPropertyOfType(sourceType, propName) : elementType; if (type) { - return checkDestructuringAssignment(element, type, checkMode); + return checkDestructuringAssignment(element, type, checkMode, granular); } else { // We still need to check element expression here because we may need to set appropriate flag on the expression @@ -17329,7 +17332,7 @@ namespace ts { error((restExpression).operatorToken, Diagnostics.A_rest_element_cannot_have_an_initializer); } else { - return checkDestructuringAssignment(restExpression, createArrayType(elementType), checkMode); + return checkDestructuringAssignment(restExpression, createArrayType(elementType), checkMode, granular); } } } @@ -17337,7 +17340,7 @@ namespace ts { return undefined; } - function checkDestructuringAssignment(exprOrAssignment: Expression | ShorthandPropertyAssignment, sourceType: Type, checkMode?: CheckMode): Type { + function checkDestructuringAssignment(exprOrAssignment: Expression | ShorthandPropertyAssignment, sourceType: Type, checkMode?: CheckMode, granular?: boolean): Type { let target: Expression; if (exprOrAssignment.kind === SyntaxKind.ShorthandPropertyAssignment) { const prop = exprOrAssignment; @@ -17348,7 +17351,7 @@ namespace ts { !(getFalsyFlags(checkExpression(prop.objectAssignmentInitializer)) & TypeFlags.Undefined)) { sourceType = getTypeWithFacts(sourceType, TypeFacts.NEUndefined); } - checkBinaryLikeExpression(prop.name, prop.equalsToken, prop.objectAssignmentInitializer, checkMode); + checkBinaryLikeExpression(prop.name, prop.equalsToken, prop.objectAssignmentInitializer, checkMode, granular); } target = (exprOrAssignment).name; } @@ -17357,20 +17360,20 @@ namespace ts { } if (target.kind === SyntaxKind.BinaryExpression && (target).operatorToken.kind === SyntaxKind.EqualsToken) { - checkBinaryExpression(target, checkMode); + checkBinaryExpression(target, checkMode, granular); target = (target).left; } if (target.kind === SyntaxKind.ObjectLiteralExpression) { return checkObjectLiteralAssignment(target, sourceType); } if (target.kind === SyntaxKind.ArrayLiteralExpression) { - return checkArrayLiteralAssignment(target, sourceType, checkMode); + return checkArrayLiteralAssignment(target, sourceType, checkMode, granular); } - return checkReferenceAssignment(target, sourceType, checkMode); + return checkReferenceAssignment(target, sourceType, checkMode, granular); } - function checkReferenceAssignment(target: Expression, sourceType: Type, checkMode?: CheckMode): Type { - const targetType = checkExpression(target, checkMode); + function checkReferenceAssignment(target: Expression, sourceType: Type, checkMode?: CheckMode, granular?: boolean): Type { + const targetType = checkExpression(target, checkMode, granular); const error = target.parent.kind === SyntaxKind.SpreadAssignment ? Diagnostics.The_target_of_an_object_rest_assignment_must_be_a_variable_or_a_property_access : Diagnostics.The_left_hand_side_of_an_assignment_expression_must_be_a_variable_or_a_property_access; @@ -17458,17 +17461,17 @@ namespace ts { getUnionType([type1, type2], /*subtypeReduction*/ true); } - function checkBinaryExpression(node: BinaryExpression, checkMode?: CheckMode) { - return checkBinaryLikeExpression(node.left, node.operatorToken, node.right, checkMode, node); + function checkBinaryExpression(node: BinaryExpression, checkMode?: CheckMode, granular?: boolean) { + return checkBinaryLikeExpression(node.left, node.operatorToken, node.right, checkMode, granular, node); } - function checkBinaryLikeExpression(left: Expression, operatorToken: Node, right: Expression, checkMode?: CheckMode, errorNode?: Node) { + function checkBinaryLikeExpression(left: Expression, operatorToken: Node, right: Expression, checkMode?: CheckMode, granular?: boolean, errorNode?: Node) { const operator = operatorToken.kind; if (operator === SyntaxKind.EqualsToken && (left.kind === SyntaxKind.ObjectLiteralExpression || left.kind === SyntaxKind.ArrayLiteralExpression)) { - return checkDestructuringAssignment(left, checkExpression(right, checkMode), checkMode); + return checkDestructuringAssignment(left, checkExpression(right, checkMode, granular), checkMode, granular); } - let leftType = checkExpression(left, checkMode); - let rightType = checkExpression(right, checkMode); + let leftType = checkExpression(left, checkMode, granular); + let rightType = checkExpression(right, checkMode, granular); switch (operator) { case SyntaxKind.AsteriskToken: case SyntaxKind.AsteriskAsteriskToken: @@ -17750,10 +17753,10 @@ namespace ts { return anyType; } - function checkConditionalExpression(node: ConditionalExpression, checkMode?: CheckMode): Type { + function checkConditionalExpression(node: ConditionalExpression, checkMode?: CheckMode, granular?: boolean): Type { checkExpression(node.condition); - const type1 = checkExpression(node.whenTrue, checkMode); - const type2 = checkExpression(node.whenFalse, checkMode); + const type1 = checkExpression(node.whenTrue, checkMode, granular); + const type2 = checkExpression(node.whenFalse, checkMode, granular); return getBestChoiceType(type1, type2); } @@ -17792,13 +17795,13 @@ namespace ts { node.contextualMapper = contextualMapper; const checkMode = contextualMapper === identityMapper ? CheckMode.SkipContextSensitive : contextualMapper ? CheckMode.Inferential : CheckMode.Normal; - const result = checkExpression(node, checkMode); + const result = checkExpression(node, checkMode, granularConst); node.contextualType = saveContextualType; node.contextualMapper = saveContextualMapper; return result; } - function checkExpressionCached(node: Expression, checkMode?: CheckMode): Type { + function checkExpressionCached(node: Expression, checkMode?: CheckMode, granular?: boolean): Type { const links = getNodeLinks(node); if (!links.resolvedType) { // When computing a type that we're going to cache, we need to ignore any ongoing control flow @@ -17806,7 +17809,8 @@ namespace ts { // to the top of the stack ensures all transient types are computed from a known point. const saveFlowLoopStart = flowLoopStart; flowLoopStart = flowLoopCount; - links.resolvedType = checkExpression(node, checkMode); + // every node is expected to always be checked using the same options, so no mix-ups + links.resolvedType = checkExpression(node, checkMode, granular); flowLoopStart = saveFlowLoopStart; } return links.resolvedType; @@ -17817,8 +17821,9 @@ namespace ts { return node.kind === SyntaxKind.TypeAssertionExpression || node.kind === SyntaxKind.AsExpression; } - function checkDeclarationInitializer(declaration: VariableLikeDeclaration) { - const type = getTypeOfExpression(declaration.initializer, /*cache*/ true); + function checkDeclarationInitializer(declaration: VariableLikeDeclaration): Type { + const granular = granularConst && !!(getCombinedNodeFlags(declaration) & NodeFlags.Const); + const type = getTypeOfExpression(declaration.initializer, /*cache*/ true, granular); return getCombinedNodeFlags(declaration) & NodeFlags.Const || getCombinedModifierFlags(declaration) & ModifierFlags.Readonly && !isParameterPropertyDeclaration(declaration) || isTypeAssertion(declaration.initializer) ? type : getWidenedLiteralType(type); @@ -17841,12 +17846,12 @@ namespace ts { return false; } - function checkExpressionForMutableLocation(node: Expression, checkMode?: CheckMode): Type { - const type = checkExpression(node, checkMode); - return isTypeAssertion(node) || isLiteralContextualType(getContextualType(node)) ? type : getWidenedLiteralType(type); + function checkExpressionForMutableLocation(node: Expression, checkMode?: CheckMode, granular?: boolean): Type { + const type = checkExpression(node, checkMode, granular); + return granular || isTypeAssertion(node) || isLiteralContextualType(getContextualType(node)) ? type : getWidenedLiteralType(type); } - function checkPropertyAssignment(node: PropertyAssignment, checkMode?: CheckMode): Type { + function checkPropertyAssignment(node: PropertyAssignment, checkMode?: CheckMode, granular?: boolean): Type { // Do not use hasDynamicName here, because that returns false for well known symbols. // We want to perform checkComputedPropertyName for all computed properties, including // well known symbols. @@ -17854,10 +17859,10 @@ namespace ts { checkComputedPropertyName(node.name); } - return checkExpressionForMutableLocation((node).initializer, checkMode); + return checkExpressionForMutableLocation((node).initializer, checkMode, granular); } - function checkObjectLiteralMethod(node: MethodDeclaration, checkMode?: CheckMode): Type { + function checkObjectLiteralMethod(node: MethodDeclaration, checkMode?: CheckMode, granular?: boolean): Type { // Grammar checking checkGrammarMethod(node); @@ -17868,7 +17873,7 @@ namespace ts { checkComputedPropertyName(node.name); } - const uninstantiatedType = checkFunctionExpressionOrObjectLiteralMethod(node, checkMode); + const uninstantiatedType = checkFunctionExpressionOrObjectLiteralMethod(node, checkMode, granular); return instantiateTypeWithSingleGenericCallSignature(node, uninstantiatedType, checkMode); } @@ -17895,7 +17900,7 @@ namespace ts { * A cache argument of true indicates that if the function performs a full type check, it is ok * to cache the result. */ - function getTypeOfExpression(node: Expression, cache?: boolean) { + function getTypeOfExpression(node: Expression, cache?: boolean, granular?: boolean): Type { // Optimize for the common case of a call to a function with a single non-generic call // signature where we can just fetch the return type without checking the arguments. if (node.kind === SyntaxKind.CallExpression && (node).expression.kind !== SyntaxKind.SuperKeyword && !isRequireCall(node, /*checkArgumentIsStringLiteral*/ true)) { @@ -17908,7 +17913,8 @@ namespace ts { // Otherwise simply call checkExpression. Ideally, the entire family of checkXXX functions // should have a parameter that indicates whether full error checking is required such that // we can perform the optimizations locally. - return cache ? checkExpressionCached(node) : checkExpression(node); + const checkMode = CheckMode.Normal; + return cache ? checkExpressionCached(node, checkMode, granular) : checkExpression(node, checkMode, granular); } /** @@ -17933,13 +17939,13 @@ namespace ts { // object, it serves as an indicator that all contained function and arrow expressions should be considered to // have the wildcard function type; this form of type check is used during overload resolution to exclude // contextually typed function and arrow expressions in the initial phase. - function checkExpression(node: Expression | QualifiedName, checkMode?: CheckMode): Type { + function checkExpression(node: Expression | QualifiedName, checkMode?: CheckMode, granular?: boolean): Type { let type: Type; if (node.kind === SyntaxKind.QualifiedName) { type = checkQualifiedName(node); } else { - const uninstantiatedType = checkExpressionWorker(node, checkMode); + const uninstantiatedType = checkExpressionWorker(node, checkMode, granular); type = instantiateTypeWithSingleGenericCallSignature(node, uninstantiatedType, checkMode); } @@ -17960,19 +17966,19 @@ namespace ts { return type; } - function checkParenthesizedExpression(node: ParenthesizedExpression, checkMode?: CheckMode): Type { + function checkParenthesizedExpression(node: ParenthesizedExpression, checkMode?: CheckMode, granular?: boolean): Type { if (isInJavaScriptFile(node) && node.jsDoc) { const typecasts = flatMap(node.jsDoc, doc => filter(doc.tags, tag => tag.kind === SyntaxKind.JSDocTypeTag)); if (typecasts && typecasts.length) { // We should have already issued an error if there were multiple type jsdocs const cast = typecasts[0] as JSDocTypeTag; - return checkAssertionWorker(cast, cast.typeExpression.type, node.expression, checkMode); + return checkAssertionWorker(cast, cast.typeExpression.type, node.expression, checkMode, granular); } } - return checkExpression(node.expression, checkMode); + return checkExpression(node.expression, checkMode, granular); } - function checkExpressionWorker(node: Expression, checkMode: CheckMode): Type { + function checkExpressionWorker(node: Expression, checkMode: CheckMode, granular?: boolean): Type { switch (node.kind) { case SyntaxKind.Identifier: return checkIdentifier(node); @@ -17993,9 +17999,9 @@ namespace ts { case SyntaxKind.RegularExpressionLiteral: return globalRegExpType; case SyntaxKind.ArrayLiteralExpression: - return checkArrayLiteral(node, checkMode); + return checkArrayLiteral(node, checkMode, granular); case SyntaxKind.ObjectLiteralExpression: - return checkObjectLiteral(node, checkMode); + return checkObjectLiteral(node, checkMode, granular); case SyntaxKind.PropertyAccessExpression: return checkPropertyAccessExpression(node); case SyntaxKind.ElementAccessExpression: @@ -23055,7 +23061,7 @@ namespace ts { if (isRightSideOfQualifiedNameOrPropertyAccess(expr)) { expr = expr.parent; } - return getRegularTypeOfLiteralType(getTypeOfExpression(expr)); + return getRegularTypeOfLiteralType(getTypeOfExpression(expr, granularConst)); } /** diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index c92d147f9a93a..bff3e3c226029 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -269,6 +269,13 @@ namespace ts { category: Diagnostics.Strict_Type_Checking_Options, description: Diagnostics.Enable_strict_null_checks }, + { + name: "widenTypes", + type: "boolean", + showInSimplifiedHelpView: true, + category: Diagnostics.Strict_Type_Checking_Options, + description: Diagnostics.Automatically_widen_types_even_in_params_and_const + }, { name: "noImplicitThis", type: "boolean", diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 77e7f7e7b6246..c036f753380d2 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3302,6 +3302,10 @@ "category": "Message", "code": 6185 }, + "Automatically widen types even in params and `const`.": { + "category": "Message", + "code": 6186 + }, "Variable '{0}' implicitly has an '{1}' type.": { "category": "Error", "code": 7005 diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 0a09bb5b6ecba..9959110c39ea7 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3622,6 +3622,7 @@ namespace ts { sourceRoot?: string; strict?: boolean; strictNullChecks?: boolean; // Always combine with strict property + widenTypes?: boolean; /* @internal */ stripInternal?: boolean; suppressExcessPropertyErrors?: boolean; suppressImplicitAnyIndexErrors?: boolean; diff --git a/src/harness/unittests/configurationExtension.ts b/src/harness/unittests/configurationExtension.ts index 2d50d2cb2af97..6a894c61aeb84 100644 --- a/src/harness/unittests/configurationExtension.ts +++ b/src/harness/unittests/configurationExtension.ts @@ -16,6 +16,12 @@ namespace ts { strictNullChecks: false } }, + "/dev/tsconfig.widenTypes.json": { + extends: "./tsconfig", + compilerOptions: { + widenTypes: false + } + }, "/dev/configs/base.json": { compilerOptions: { allowJs: true, diff --git a/src/harness/unittests/transpile.ts b/src/harness/unittests/transpile.ts index e0c96797827f0..c76f398bb77bc 100644 --- a/src/harness/unittests/transpile.ts +++ b/src/harness/unittests/transpile.ts @@ -413,6 +413,10 @@ var x = 0;`, { options: { compilerOptions: { strictNullChecks: true }, fileName: "input.js", reportDiagnostics: true } }); + transpilesCorrectly("Supports setting 'widenTypes'", "x;", { + options: { compilerOptions: { widenTypes: true }, fileName: "input.js", reportDiagnostics: true } + }); + transpilesCorrectly("Supports setting 'stripInternal'", "x;", { options: { compilerOptions: { stripInternal: true }, fileName: "input.js", reportDiagnostics: true } }); diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 0b7405c7b6986..e4ccb8305b428 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -2448,6 +2448,7 @@ namespace ts.server.protocol { sourceRoot?: string; strict?: boolean; strictNullChecks?: boolean; + widenTypes?: boolean; suppressExcessPropertyErrors?: boolean; suppressImplicitAnyIndexErrors?: boolean; target?: ScriptTarget | ts.ScriptTarget; diff --git a/tests/baselines/reference/dontWiden.js b/tests/baselines/reference/dontWiden.js new file mode 100644 index 0000000000000..f9a19b5ab7320 --- /dev/null +++ b/tests/baselines/reference/dontWiden.js @@ -0,0 +1,41 @@ +//// [dontWiden.ts] +const c = [1, 'a']; +const d = { a: 1, b: 'c' }; + +interface SomeInterface { a: boolean } +declare function foo(arg: T): { hi: T }; +declare function boo(arg: T): { hi: T }; +declare function bar(arg: SomeInterface): void +declare function baz(arg: [number, 2, 3 | number]): void +declare function bag(arg: number[]): void + +// As variable assignees +const a: number[] = [1, 2, 3]; +const b: SomeInterface = {a: true}; +const e = [1, 2, 3]; + +// Same, but as arguments +foo([1, 2, 3]); +bar({a: true}); +baz([1, 2, 3]); +bag([1, 2, 3]); +bag(e); +boo([1, 2, 3]); +boo(e); + + +//// [dontWiden.js] +var c = [1, 'a']; +var d = { a: 1, b: 'c' }; +// As variable assignees +var a = [1, 2, 3]; +var b = { a: true }; +var e = [1, 2, 3]; +// Same, but as arguments +foo([1, 2, 3]); +bar({ a: true }); +baz([1, 2, 3]); +bag([1, 2, 3]); +bag(e); +boo([1, 2, 3]); +boo(e); diff --git a/tests/baselines/reference/dontWiden.symbols b/tests/baselines/reference/dontWiden.symbols new file mode 100644 index 0000000000000..a38508e9696f4 --- /dev/null +++ b/tests/baselines/reference/dontWiden.symbols @@ -0,0 +1,79 @@ +=== tests/cases/compiler/dontWiden.ts === +const c = [1, 'a']; +>c : Symbol(c, Decl(dontWiden.ts, 0, 5)) + +const d = { a: 1, b: 'c' }; +>d : Symbol(d, Decl(dontWiden.ts, 1, 5)) +>a : Symbol(a, Decl(dontWiden.ts, 1, 11)) +>b : Symbol(b, Decl(dontWiden.ts, 1, 17)) + +interface SomeInterface { a: boolean } +>SomeInterface : Symbol(SomeInterface, Decl(dontWiden.ts, 1, 27)) +>a : Symbol(SomeInterface.a, Decl(dontWiden.ts, 3, 25)) + +declare function foo(arg: T): { hi: T }; +>foo : Symbol(foo, Decl(dontWiden.ts, 3, 38)) +>T : Symbol(T, Decl(dontWiden.ts, 4, 21)) +>arg : Symbol(arg, Decl(dontWiden.ts, 4, 38)) +>T : Symbol(T, Decl(dontWiden.ts, 4, 21)) +>hi : Symbol(hi, Decl(dontWiden.ts, 4, 48)) +>T : Symbol(T, Decl(dontWiden.ts, 4, 21)) + +declare function boo(arg: T): { hi: T }; +>boo : Symbol(boo, Decl(dontWiden.ts, 4, 57)) +>T : Symbol(T, Decl(dontWiden.ts, 5, 21)) +>arg : Symbol(arg, Decl(dontWiden.ts, 5, 41)) +>T : Symbol(T, Decl(dontWiden.ts, 5, 21)) +>hi : Symbol(hi, Decl(dontWiden.ts, 5, 51)) +>T : Symbol(T, Decl(dontWiden.ts, 5, 21)) + +declare function bar(arg: SomeInterface): void +>bar : Symbol(bar, Decl(dontWiden.ts, 5, 60)) +>arg : Symbol(arg, Decl(dontWiden.ts, 6, 21)) +>SomeInterface : Symbol(SomeInterface, Decl(dontWiden.ts, 1, 27)) + +declare function baz(arg: [number, 2, 3 | number]): void +>baz : Symbol(baz, Decl(dontWiden.ts, 6, 46)) +>arg : Symbol(arg, Decl(dontWiden.ts, 7, 21)) + +declare function bag(arg: number[]): void +>bag : Symbol(bag, Decl(dontWiden.ts, 7, 56)) +>arg : Symbol(arg, Decl(dontWiden.ts, 8, 21)) + +// As variable assignees +const a: number[] = [1, 2, 3]; +>a : Symbol(a, Decl(dontWiden.ts, 11, 5)) + +const b: SomeInterface = {a: true}; +>b : Symbol(b, Decl(dontWiden.ts, 12, 5)) +>SomeInterface : Symbol(SomeInterface, Decl(dontWiden.ts, 1, 27)) +>a : Symbol(a, Decl(dontWiden.ts, 12, 26)) + +const e = [1, 2, 3]; +>e : Symbol(e, Decl(dontWiden.ts, 13, 5)) + +// Same, but as arguments +foo([1, 2, 3]); +>foo : Symbol(foo, Decl(dontWiden.ts, 3, 38)) + +bar({a: true}); +>bar : Symbol(bar, Decl(dontWiden.ts, 5, 60)) +>a : Symbol(a, Decl(dontWiden.ts, 17, 5)) + +baz([1, 2, 3]); +>baz : Symbol(baz, Decl(dontWiden.ts, 6, 46)) + +bag([1, 2, 3]); +>bag : Symbol(bag, Decl(dontWiden.ts, 7, 56)) + +bag(e); +>bag : Symbol(bag, Decl(dontWiden.ts, 7, 56)) +>e : Symbol(e, Decl(dontWiden.ts, 13, 5)) + +boo([1, 2, 3]); +>boo : Symbol(boo, Decl(dontWiden.ts, 4, 57)) + +boo(e); +>boo : Symbol(boo, Decl(dontWiden.ts, 4, 57)) +>e : Symbol(e, Decl(dontWiden.ts, 13, 5)) + diff --git a/tests/baselines/reference/dontWiden.types b/tests/baselines/reference/dontWiden.types new file mode 100644 index 0000000000000..40ffa6344b583 --- /dev/null +++ b/tests/baselines/reference/dontWiden.types @@ -0,0 +1,120 @@ +=== tests/cases/compiler/dontWiden.ts === +const c = [1, 'a']; +>c : [1, "a"] +>[1, 'a'] : [1, "a"] +>1 : 1 +>'a' : "a" + +const d = { a: 1, b: 'c' }; +>d : { a: 1; b: "c"; } +>{ a: 1, b: 'c' } : { a: 1; b: "c"; } +>a : number +>1 : 1 +>b : string +>'c' : "c" + +interface SomeInterface { a: boolean } +>SomeInterface : SomeInterface +>a : boolean + +declare function foo(arg: T): { hi: T }; +>foo : (arg: T) => { hi: T; } +>T : T +>arg : T +>T : T +>hi : T +>T : T + +declare function boo(arg: T): { hi: T }; +>boo : (arg: T) => { hi: T; } +>T : T +>arg : T +>T : T +>hi : T +>T : T + +declare function bar(arg: SomeInterface): void +>bar : (arg: SomeInterface) => void +>arg : SomeInterface +>SomeInterface : SomeInterface + +declare function baz(arg: [number, 2, 3 | number]): void +>baz : (arg: [number, 2, number]) => void +>arg : [number, 2, number] + +declare function bag(arg: number[]): void +>bag : (arg: number[]) => void +>arg : number[] + +// As variable assignees +const a: number[] = [1, 2, 3]; +>a : number[] +>[1, 2, 3] : number[] +>1 : 1 +>2 : 2 +>3 : 3 + +const b: SomeInterface = {a: true}; +>b : SomeInterface +>SomeInterface : SomeInterface +>{a: true} : { a: true; } +>a : boolean +>true : true + +const e = [1, 2, 3]; +>e : [1, 2, 3] +>[1, 2, 3] : [1, 2, 3] +>1 : 1 +>2 : 2 +>3 : 3 + +// Same, but as arguments +foo([1, 2, 3]); +>foo([1, 2, 3]) : { hi: [1, 2, 3]; } +>foo : (arg: T) => { hi: T; } +>[1, 2, 3] : [1, 2, 3] +>1 : 1 +>2 : 2 +>3 : 3 + +bar({a: true}); +>bar({a: true}) : void +>bar : (arg: SomeInterface) => void +>{a: true} : { a: true; } +>a : boolean +>true : true + +baz([1, 2, 3]); +>baz([1, 2, 3]) : void +>baz : (arg: [number, 2, number]) => void +>[1, 2, 3] : [number, 2, number] +>1 : 1 +>2 : 2 +>3 : 3 + +bag([1, 2, 3]); +>bag([1, 2, 3]) : void +>bag : (arg: number[]) => void +>[1, 2, 3] : number[] +>1 : 1 +>2 : 2 +>3 : 3 + +bag(e); +>bag(e) : void +>bag : (arg: number[]) => void +>e : [1, 2, 3] + +boo([1, 2, 3]); +>boo([1, 2, 3]) : { hi: [1, 2, 3]; } +>boo : (arg: T) => { hi: T; } +>[1, 2, 3] : [1, 2, 3] +>1 : 1 +>2 : 2 +>3 : 3 + +boo(e); +>boo(e) : { hi: [1, 2, 3]; } +>boo : (arg: T) => { hi: T; } +>e : [1, 2, 3] + diff --git a/tests/baselines/reference/transpile/Supports setting granularConst.js b/tests/baselines/reference/transpile/Supports setting granularConst.js new file mode 100644 index 0000000000000..8394371f9081a --- /dev/null +++ b/tests/baselines/reference/transpile/Supports setting granularConst.js @@ -0,0 +1,2 @@ +x; +//# sourceMappingURL=input.js.map \ No newline at end of file diff --git a/tests/baselines/reference/transpile/Supports setting widenTypes.js b/tests/baselines/reference/transpile/Supports setting widenTypes.js new file mode 100644 index 0000000000000..8394371f9081a --- /dev/null +++ b/tests/baselines/reference/transpile/Supports setting widenTypes.js @@ -0,0 +1,2 @@ +x; +//# sourceMappingURL=input.js.map \ No newline at end of file diff --git a/tests/baselines/reference/tsConfig/Default initialized TSConfig/tsconfig.json b/tests/baselines/reference/tsConfig/Default initialized TSConfig/tsconfig.json index 0f5b23784683f..d6338e70241dd 100644 --- a/tests/baselines/reference/tsConfig/Default initialized TSConfig/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Default initialized TSConfig/tsconfig.json @@ -22,6 +22,7 @@ "strict": true /* Enable all strict type-checking options. */ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ + // "widenTypes": true, /* Automatically widen types even in params and `const`. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with boolean value compiler options/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with boolean value compiler options/tsconfig.json index a545124a72397..4be8df9a99bf7 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with boolean value compiler options/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with boolean value compiler options/tsconfig.json @@ -22,6 +22,7 @@ "strict": true, /* Enable all strict type-checking options. */ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ + // "widenTypes": true, /* Automatically widen types even in params and `const`. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with enum value compiler options/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with enum value compiler options/tsconfig.json index b53ac2d8552d8..95af7d24ed78e 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with enum value compiler options/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with enum value compiler options/tsconfig.json @@ -22,6 +22,7 @@ "strict": true /* Enable all strict type-checking options. */ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ + // "widenTypes": true, /* Automatically widen types even in params and `const`. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with files options/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with files options/tsconfig.json index 4e06e06d159a4..cffa372e37644 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with files options/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with files options/tsconfig.json @@ -22,6 +22,7 @@ "strict": true /* Enable all strict type-checking options. */ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ + // "widenTypes": true, /* Automatically widen types even in params and `const`. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option value/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option value/tsconfig.json index 94808d89ed05d..2f99be22cae15 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option value/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option value/tsconfig.json @@ -22,6 +22,7 @@ "strict": true /* Enable all strict type-checking options. */ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ + // "widenTypes": true, /* Automatically widen types even in params and `const`. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option/tsconfig.json index 0f5b23784683f..d6338e70241dd 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option/tsconfig.json @@ -22,6 +22,7 @@ "strict": true /* Enable all strict type-checking options. */ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ + // "widenTypes": true, /* Automatically widen types even in params and `const`. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options with enum value/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options with enum value/tsconfig.json index d165b0f277541..ff71d3de67aaf 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options with enum value/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options with enum value/tsconfig.json @@ -22,6 +22,7 @@ "strict": true /* Enable all strict type-checking options. */ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ + // "widenTypes": true, /* Automatically widen types even in params and `const`. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options/tsconfig.json index 2a169b3aaafa1..ddc0a670e2e39 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options/tsconfig.json @@ -22,6 +22,7 @@ "strict": true, /* Enable all strict type-checking options. */ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ + // "widenTypes": true, /* Automatically widen types even in params and `const`. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ diff --git a/tests/cases/compiler/dontWiden.ts b/tests/cases/compiler/dontWiden.ts new file mode 100644 index 0000000000000..c6bd6e7484376 --- /dev/null +++ b/tests/cases/compiler/dontWiden.ts @@ -0,0 +1,25 @@ +// @widenTypes: false + +const c = [1, 'a']; +const d = { a: 1, b: 'c' }; + +interface SomeInterface { a: boolean } +declare function foo(arg: T): { hi: T }; +declare function boo(arg: T): { hi: T }; +declare function bar(arg: SomeInterface): void +declare function baz(arg: [number, 2, 3 | number]): void +declare function bag(arg: number[]): void + +// As variable assignees +const a: number[] = [1, 2, 3]; +const b: SomeInterface = {a: true}; +const e = [1, 2, 3]; + +// Same, but as arguments +foo([1, 2, 3]); +bar({a: true}); +baz([1, 2, 3]); +bag([1, 2, 3]); +bag(e); +boo([1, 2, 3]); +boo(e); From a6dc21d6ae3f12f0d15ae7238a033de6d683e409 Mon Sep 17 00:00:00 2001 From: Tycho Grouwstra Date: Thu, 17 Aug 2017 14:42:20 +0800 Subject: [PATCH 2/2] update spec --- doc/spec.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/spec.md b/doc/spec.md index 5c9ccdc0fdeb2..be7ac79598448 100644 --- a/doc/spec.md +++ b/doc/spec.md @@ -2630,7 +2630,7 @@ Each element expression in a non-empty array literal is processed as follows: The resulting type an array literal expression is determined as follows: -* If the array literal is empty, the resulting type is an empty tuple type in `granularConst` mode, otherwise an array type with the element type Undefined. +* If the array literal is empty, the resulting type is an empty tuple type if `widenTypes` is disabled, otherwise an array type with the element type Undefined. * Otherwise, if the array literal contains no spread elements and is contextually typed by a tuple-like type (section [3.3.3](#3.3.3)), the resulting type is a tuple type constructed from the types of the element expressions. * Otherwise, if the array literal contains no spread elements and is an array assignment pattern in a destructuring assignment (section [4.21.1](#4.21.1)), the resulting type is a tuple type constructed from the types of the element expressions. * Otherwise, the resulting type is an array type with an element type that is the union of the types of the non-spread element expressions and the numeric index signature types of the spread element expressions.