diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 04dc4011a1..41bf7b58c1 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1045,7 +1045,35 @@ namespace ts { if (node.finallyBlock) { // in finally flow is combined from pre-try/flow from try/flow from catch // pre-flow is necessary to make sure that finally is reachable even if finally flows in both try and finally blocks are unreachable - addAntecedent(preFinallyLabel, preTryFlow); + + // also for finally blocks we inject two extra edges into the flow graph. + // first -> edge that connects pre-try flow with the label at the beginning of the finally block, it has lock associated with it + // second -> edge that represents post-finally flow. + // these edges are used in following scenario: + // let a; (1) + // try { a = someOperation(); (2)} + // finally { (3) console.log(a) } (4) + // (5) a + + // flow graph for this case looks roughly like this (arrows show ): + // (1-pre-try-flow) <--.. <-- (2-post-try-flow) + // ^ ^ + // |*****(3-pre-finally-label) -----| + // ^ + // |-- ... <-- (4-post-finally-label) <--- (5) + // In case when we walk the flow starting from inside the finally block we want to take edge '*****' into account + // since it ensures that finally is always reachable. However when we start outside the finally block and go through label (5) + // then edge '*****' should be discarded because label 4 is only reachable if post-finally label-4 is reachable + // Simply speaking code inside finally block is treated as reachable as pre-try-flow + // since we conservatively assume that any line in try block can throw or return in which case we'll enter finally. + // However code after finally is reachable only if control flow was not abrupted in try/catch or finally blocks - it should be composed from + // final flows of these blocks without taking pre-try flow into account. + // + // extra edges that we inject allows to control this behavior + // if when walking the flow we step on post-finally edge - we can mark matching pre-finally edge as locked so it will be skipped. + const preFinallyFlow: PreFinallyFlow = { flags: FlowFlags.PreFinally, antecedent: preTryFlow, lock: {} }; + addAntecedent(preFinallyLabel, preFinallyFlow); + currentFlow = finishFlowLabel(preFinallyLabel); bind(node.finallyBlock); // if flow after finally is unreachable - keep it @@ -1061,6 +1089,11 @@ namespace ts { : unreachableFlow; } } + if (!(currentFlow.flags & FlowFlags.Unreachable)) { + const afterFinallyFlow: AfterFinallyFlow = { flags: FlowFlags.AfterFinally, antecedent: currentFlow }; + preFinallyFlow.lock = afterFinallyFlow; + currentFlow = afterFinallyFlow; + } } else { currentFlow = finishFlowLabel(preFinallyLabel); diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 394e95598e..f2c9dcd608 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2690,7 +2690,11 @@ namespace ts { writePunctuation(writer, SyntaxKind.ColonToken); writeSpace(writer); - buildTypeDisplay(getTypeOfSymbol(p), writer, enclosingDeclaration, flags, symbolStack); + let type = getTypeOfSymbol(p); + if (isRequiredInitializedParameter(parameterNode)) { + type = includeFalsyTypes(type, TypeFlags.Undefined); + } + buildTypeDisplay(type, writer, enclosingDeclaration, flags, symbolStack); } function buildBindingPatternDisplay(bindingPattern: BindingPattern, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags, symbolStack?: Symbol[]) { @@ -3271,6 +3275,16 @@ namespace ts { return strictNullChecks && optional ? includeFalsyTypes(type, TypeFlags.Undefined) : type; } + /** remove undefined from the annotated type of a parameter when there is an initializer (that doesn't include undefined) */ + function removeOptionalityFromAnnotation(annotatedType: Type, declaration: VariableLikeDeclaration): Type { + const annotationIncludesUndefined = strictNullChecks && + declaration.kind === SyntaxKind.Parameter && + declaration.initializer && + getFalsyFlags(annotatedType) & TypeFlags.Undefined && + !(getFalsyFlags(checkExpression(declaration.initializer)) & TypeFlags.Undefined); + return annotationIncludesUndefined ? getNonNullableType(annotatedType) : annotatedType; + } + // Return the inferred type for a variable, parameter, or property declaration function getTypeForVariableLikeDeclaration(declaration: VariableLikeDeclaration, includeOptionality: boolean): Type { if (declaration.flags & NodeFlags.JavaScriptFile) { @@ -3304,7 +3318,8 @@ namespace ts { // Use type from type annotation if one is present if (declaration.type) { - return addOptionality(getTypeFromTypeNode(declaration.type), /*optional*/ declaration.questionToken && includeOptionality); + const declaredType = removeOptionalityFromAnnotation(getTypeFromTypeNode(declaration.type), declaration); + return addOptionality(declaredType, /*optional*/ declaration.questionToken && includeOptionality); } if ((compilerOptions.noImplicitAny || declaration.flags & NodeFlags.JavaScriptFile) && @@ -5198,6 +5213,12 @@ namespace ts { Debug.assert(parameterIndex >= 0); return parameterIndex >= signature.minArgumentCount; } + const iife = getImmediatelyInvokedFunctionExpression(node.parent); + if (iife) { + return !node.type && + !node.dotDotDotToken && + indexOf((node.parent as SignatureDeclaration).parameters, node) >= iife.arguments.length; + } return false; } @@ -7762,16 +7783,36 @@ namespace ts { if (target.flags & TypeFlags.Union && containsType(targetTypes, source)) { return Ternary.True; } - const len = targetTypes.length; - for (let i = 0; i < len; i++) { - const related = isRelatedTo(source, targetTypes[i], reportErrors && i === len - 1); + for (const type of targetTypes) { + const related = isRelatedTo(source, type, /*reportErrors*/ false); if (related) { return related; } } + if (reportErrors) { + const discriminantType = findMatchingDiscriminantType(source, target); + isRelatedTo(source, discriminantType || targetTypes[targetTypes.length - 1], /*reportErrors*/ true); + } return Ternary.False; } + function findMatchingDiscriminantType(source: Type, target: UnionOrIntersectionType) { + const sourceProperties = getPropertiesOfObjectType(source); + if (sourceProperties) { + for (const sourceProperty of sourceProperties) { + if (isDiscriminantProperty(target, sourceProperty.name)) { + const sourceType = getTypeOfSymbol(sourceProperty); + for (const type of target.types) { + const targetType = getTypeOfPropertyOfType(type, sourceProperty.name); + if (targetType && isRelatedTo(sourceType, targetType)) { + return type; + } + } + } + } + } + } + function typeRelatedToEachType(source: Type, target: UnionOrIntersectionType, reportErrors: boolean): Ternary { let result = Ternary.True; const targetTypes = target.types; @@ -8592,7 +8633,7 @@ namespace ts { if (flags & TypeFlags.Void) types.push(voidType); if (flags & TypeFlags.Undefined) types.push(undefinedType); if (flags & TypeFlags.Null) types.push(nullType); - return getUnionType(types, /*subtypeReduction*/ true); + return getUnionType(types); } function removeDefinitelyFalsyTypes(type: Type): Type { @@ -9875,7 +9916,19 @@ namespace ts { } } let type: FlowType; - if (flow.flags & FlowFlags.Assignment) { + if (flow.flags & FlowFlags.AfterFinally) { + // block flow edge: finally -> pre-try (for larger explanation check comment in binder.ts - bindTryStatement + (flow).locked = true; + type = getTypeAtFlowNode((flow).antecedent); + (flow).locked = false; + } + else if (flow.flags & FlowFlags.PreFinally) { + // locked pre-finally flows are filtered out in getTypeAtFlowBranchLabel + // so here just redirect to antecedent + flow = (flow).antecedent; + continue; + } + else if (flow.flags & FlowFlags.Assignment) { type = getTypeAtFlowAssignment(flow); if (!type) { flow = (flow).antecedent; @@ -10031,6 +10084,12 @@ namespace ts { let subtypeReduction = false; let seenIncomplete = false; for (const antecedent of flow.antecedents) { + if (antecedent.flags & FlowFlags.PreFinally && (antecedent).lock.locked) { + // if flow correspond to branch from pre-try to finally and this branch is locked - this means that + // we initially have started following the flow outside the finally block. + // in this case we should ignore this branch. + continue; + } const flowType = getTypeAtFlowNode(antecedent); const type = getTypeFromFlowType(flowType); // If the type at a particular antecedent path is the declared type and the @@ -20710,6 +20769,13 @@ namespace ts { return false; } + function isRequiredInitializedParameter(parameter: ParameterDeclaration) { + return strictNullChecks && + !isOptionalParameter(parameter) && + parameter.initializer && + !(getModifierFlags(parameter) & ModifierFlags.ParameterPropertyModifier); + } + function getNodeCheckFlags(node: Node): NodeCheckFlags { node = getParseTreeNode(node); return node ? getNodeLinks(node).flags : undefined; @@ -20801,10 +20867,12 @@ namespace ts { function writeTypeOfDeclaration(declaration: AccessorDeclaration | VariableLikeDeclaration, enclosingDeclaration: Node, flags: TypeFormatFlags, writer: SymbolWriter) { // Get type of the symbol if this is the valid symbol otherwise get type at location const symbol = getSymbolOfNode(declaration); - const type = symbol && !(symbol.flags & (SymbolFlags.TypeLiteral | SymbolFlags.Signature)) + let type = symbol && !(symbol.flags & (SymbolFlags.TypeLiteral | SymbolFlags.Signature)) ? getWidenedLiteralType(getTypeOfSymbol(symbol)) : unknownType; - + if (flags & TypeFormatFlags.AddUndefined) { + type = includeFalsyTypes(type, TypeFlags.Undefined); + } getSymbolDisplayBuilder().buildTypeDisplay(type, writer, enclosingDeclaration, flags); } @@ -20903,6 +20971,7 @@ namespace ts { isTopLevelValueImportEqualsWithEntityName, isDeclarationVisible, isImplementationOfOverload, + isRequiredInitializedParameter, writeTypeOfDeclaration, writeReturnTypeOfSignatureDeclaration, writeTypeOfExpression, diff --git a/src/compiler/declarationEmitter.ts b/src/compiler/declarationEmitter.ts index 4b878adaff..f737c5e086 100644 --- a/src/compiler/declarationEmitter.ts +++ b/src/compiler/declarationEmitter.ts @@ -324,13 +324,20 @@ namespace ts { function writeTypeOfDeclaration(declaration: AccessorDeclaration | VariableLikeDeclaration, type: TypeNode, getSymbolAccessibilityDiagnostic: GetSymbolAccessibilityDiagnostic) { writer.getSymbolAccessibilityDiagnostic = getSymbolAccessibilityDiagnostic; write(": "); - if (type) { + + // use the checker's type, not the declared type, + // for non-optional initialized parameters that aren't a parameter property + const shouldUseResolverType = declaration.kind === SyntaxKind.Parameter && + resolver.isRequiredInitializedParameter(declaration as ParameterDeclaration); + if (type && !shouldUseResolverType) { // Write the type emitType(type); } else { errorNameNode = declaration.name; - resolver.writeTypeOfDeclaration(declaration, enclosingDeclaration, TypeFormatFlags.UseTypeOfFunction | TypeFormatFlags.UseTypeAliasValue, writer); + const format = TypeFormatFlags.UseTypeOfFunction | TypeFormatFlags.UseTypeAliasValue | + (shouldUseResolverType ? TypeFormatFlags.AddUndefined : 0); + resolver.writeTypeOfDeclaration(declaration, enclosingDeclaration, format, writer); errorNameNode = undefined; } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index e3128723af..4d8dcaa8ad 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2071,10 +2071,25 @@ ArrayMutation = 1 << 8, // Potential array mutation Referenced = 1 << 9, // Referenced as antecedent once Shared = 1 << 10, // Referenced as antecedent more than once + PreFinally = 1 << 11, // Injected edge that links pre-finally label and pre-try flow + AfterFinally = 1 << 12, // Injected edge that links post-finally flow with the rest of the graph Label = BranchLabel | LoopLabel, Condition = TrueCondition | FalseCondition } + export interface FlowLock { + locked?: boolean; + } + + export interface AfterFinallyFlow extends FlowNode, FlowLock { + antecedent: FlowNode; + } + + export interface PreFinallyFlow extends FlowNode { + antecedent: FlowNode; + lock: FlowLock; + } + export interface FlowNode { flags: FlowFlags; id?: number; // Node id used by flow type cache in checker @@ -2474,7 +2489,8 @@ InFirstTypeArgument = 0x00000100, // Writing first type argument of the instantiated type InTypeAlias = 0x00000200, // Writing type in type alias declaration UseTypeAliasValue = 0x00000400, // Serialize the type instead of using type-alias. This is needed when we emit declaration file. - SuppressAnyReturnType = 0x00000800, // If the return type is any-like, don't offer a return type. + SuppressAnyReturnType = 0x00000800, // If the return type is any-like, don't offer a return type. + AddUndefined = 0x00001000, // Add undefined to types of initialized, non-optional parameters } export const enum SymbolFormatFlags { @@ -2579,6 +2595,7 @@ isDeclarationVisible(node: Declaration): boolean; collectLinkedAliases(node: Identifier): Node[]; isImplementationOfOverload(node: FunctionLikeDeclaration): boolean; + isRequiredInitializedParameter(node: ParameterDeclaration): boolean; writeTypeOfDeclaration(declaration: AccessorDeclaration | VariableLikeDeclaration, enclosingDeclaration: Node, flags: TypeFormatFlags, writer: SymbolWriter): void; writeReturnTypeOfSignatureDeclaration(signatureDeclaration: SignatureDeclaration, enclosingDeclaration: Node, flags: TypeFormatFlags, writer: SymbolWriter): void; writeTypeOfExpression(expr: Expression, enclosingDeclaration: Node, flags: TypeFormatFlags, writer: SymbolWriter): void; diff --git a/tests/baselines/reference/contextuallyTypedIife.types b/tests/baselines/reference/contextuallyTypedIife.types index 15038e7dd2..676755fb34 100644 --- a/tests/baselines/reference/contextuallyTypedIife.types +++ b/tests/baselines/reference/contextuallyTypedIife.types @@ -253,8 +253,8 @@ let eleven = (o => o.a(11))({ a: function(n) { return n; } }); // missing arguments (function(x, undefined) { return x; })(42); >(function(x, undefined) { return x; })(42) : number ->(function(x, undefined) { return x; }) : (x: number, undefined: any) => number ->function(x, undefined) { return x; } : (x: number, undefined: any) => number +>(function(x, undefined) { return x; }) : (x: number, undefined?: any) => number +>function(x, undefined) { return x; } : (x: number, undefined?: any) => number >x : number >undefined : any >x : number @@ -262,8 +262,8 @@ let eleven = (o => o.a(11))({ a: function(n) { return n; } }); ((x, y, z) => 42)(); >((x, y, z) => 42)() : number ->((x, y, z) => 42) : (x: any, y: any, z: any) => number ->(x, y, z) => 42 : (x: any, y: any, z: any) => number +>((x, y, z) => 42) : (x?: any, y?: any, z?: any) => number +>(x, y, z) => 42 : (x?: any, y?: any, z?: any) => number >x : any >y : any >z : any diff --git a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.js b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.js new file mode 100644 index 0000000000..329c2fb19c --- /dev/null +++ b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.js @@ -0,0 +1,112 @@ +//// [defaultParameterAddsUndefinedWithStrictNullChecks.ts] +function f(addUndefined1 = "J", addUndefined2?: number) { + return addUndefined1.length + (addUndefined2 || 0); +} +function g(addUndefined = "J", addDefined: number) { + return addUndefined.length + addDefined; +} +let total = f() + f('a', 1) + f('b') + f(undefined, 2); +total = g('c', 3) + g(undefined, 4); + +function foo1(x: string = "string", b: number) { + x.length; +} + +function foo2(x = "string", b: number) { + x.length; // ok, should be string +} + +function foo3(x: string | undefined = "string", b: number) { + x.length; // ok, should be string +} + +function foo4(x: string | undefined = undefined, b: number) { + x; // should be string | undefined +} + + + +// .d.ts should have `string | undefined` for foo1, foo2, foo3 and foo4 +foo1(undefined, 1); +foo2(undefined, 1); +foo3(undefined, 1); +foo4(undefined, 1); + + +function removeUndefinedButNotFalse(x = true) { + if (x === false) { + return x; + } +} + +declare const cond: boolean; +function removeNothing(y = cond ? true : undefined) { + if (y !== undefined) { + if (y === false) { + return y; + } + } + return true; +} + + +//// [defaultParameterAddsUndefinedWithStrictNullChecks.js] +function f(addUndefined1, addUndefined2) { + if (addUndefined1 === void 0) { addUndefined1 = "J"; } + return addUndefined1.length + (addUndefined2 || 0); +} +function g(addUndefined, addDefined) { + if (addUndefined === void 0) { addUndefined = "J"; } + return addUndefined.length + addDefined; +} +var total = f() + f('a', 1) + f('b') + f(undefined, 2); +total = g('c', 3) + g(undefined, 4); +function foo1(x, b) { + if (x === void 0) { x = "string"; } + x.length; +} +function foo2(x, b) { + if (x === void 0) { x = "string"; } + x.length; // ok, should be string +} +function foo3(x, b) { + if (x === void 0) { x = "string"; } + x.length; // ok, should be string +} +function foo4(x, b) { + if (x === void 0) { x = undefined; } + x; // should be string | undefined +} +// .d.ts should have `string | undefined` for foo1, foo2, foo3 and foo4 +foo1(undefined, 1); +foo2(undefined, 1); +foo3(undefined, 1); +foo4(undefined, 1); +function removeUndefinedButNotFalse(x) { + if (x === void 0) { x = true; } + if (x === false) { + return x; + } +} +function removeNothing(y) { + if (y === void 0) { y = cond ? true : undefined; } + if (y !== undefined) { + if (y === false) { + return y; + } + } + return true; +} + + +//// [defaultParameterAddsUndefinedWithStrictNullChecks.d.ts] +declare function f(addUndefined1?: string, addUndefined2?: number): number; +declare function g(addUndefined: string | undefined, addDefined: number): number; +declare let total: number; +declare function foo1(x: string | undefined, b: number): void; +declare function foo2(x: string | undefined, b: number): void; +declare function foo3(x: string | undefined, b: number): void; +declare function foo4(x: string | undefined, b: number): void; +declare function removeUndefinedButNotFalse(x?: boolean): false | undefined; +declare const cond: boolean; +declare function removeNothing(y?: boolean | undefined): boolean; diff --git a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.symbols b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.symbols new file mode 100644 index 0000000000..fb0fce7dc7 --- /dev/null +++ b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.symbols @@ -0,0 +1,135 @@ +=== tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts === +function f(addUndefined1 = "J", addUndefined2?: number) { +>f : Symbol(f, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 0, 0)) +>addUndefined1 : Symbol(addUndefined1, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 0, 11)) +>addUndefined2 : Symbol(addUndefined2, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 0, 31)) + + return addUndefined1.length + (addUndefined2 || 0); +>addUndefined1.length : Symbol(String.length, Decl(lib.d.ts, --, --)) +>addUndefined1 : Symbol(addUndefined1, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 0, 11)) +>length : Symbol(String.length, Decl(lib.d.ts, --, --)) +>addUndefined2 : Symbol(addUndefined2, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 0, 31)) +} +function g(addUndefined = "J", addDefined: number) { +>g : Symbol(g, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 2, 1)) +>addUndefined : Symbol(addUndefined, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 3, 11)) +>addDefined : Symbol(addDefined, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 3, 30)) + + return addUndefined.length + addDefined; +>addUndefined.length : Symbol(String.length, Decl(lib.d.ts, --, --)) +>addUndefined : Symbol(addUndefined, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 3, 11)) +>length : Symbol(String.length, Decl(lib.d.ts, --, --)) +>addDefined : Symbol(addDefined, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 3, 30)) +} +let total = f() + f('a', 1) + f('b') + f(undefined, 2); +>total : Symbol(total, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 6, 3)) +>f : Symbol(f, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 0, 0)) +>f : Symbol(f, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 0, 0)) +>f : Symbol(f, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 0, 0)) +>f : Symbol(f, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 0, 0)) +>undefined : Symbol(undefined) + +total = g('c', 3) + g(undefined, 4); +>total : Symbol(total, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 6, 3)) +>g : Symbol(g, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 2, 1)) +>g : Symbol(g, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 2, 1)) +>undefined : Symbol(undefined) + +function foo1(x: string = "string", b: number) { +>foo1 : Symbol(foo1, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 7, 36)) +>x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 9, 14)) +>b : Symbol(b, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 9, 35)) + + x.length; +>x.length : Symbol(String.length, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 9, 14)) +>length : Symbol(String.length, Decl(lib.d.ts, --, --)) +} + +function foo2(x = "string", b: number) { +>foo2 : Symbol(foo2, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 11, 1)) +>x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 13, 14)) +>b : Symbol(b, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 13, 27)) + + x.length; // ok, should be string +>x.length : Symbol(String.length, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 13, 14)) +>length : Symbol(String.length, Decl(lib.d.ts, --, --)) +} + +function foo3(x: string | undefined = "string", b: number) { +>foo3 : Symbol(foo3, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 15, 1)) +>x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 17, 14)) +>b : Symbol(b, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 17, 47)) + + x.length; // ok, should be string +>x.length : Symbol(String.length, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 17, 14)) +>length : Symbol(String.length, Decl(lib.d.ts, --, --)) +} + +function foo4(x: string | undefined = undefined, b: number) { +>foo4 : Symbol(foo4, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 19, 1)) +>x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 21, 14)) +>undefined : Symbol(undefined) +>b : Symbol(b, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 21, 48)) + + x; // should be string | undefined +>x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 21, 14)) +} + + + +// .d.ts should have `string | undefined` for foo1, foo2, foo3 and foo4 +foo1(undefined, 1); +>foo1 : Symbol(foo1, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 7, 36)) +>undefined : Symbol(undefined) + +foo2(undefined, 1); +>foo2 : Symbol(foo2, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 11, 1)) +>undefined : Symbol(undefined) + +foo3(undefined, 1); +>foo3 : Symbol(foo3, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 15, 1)) +>undefined : Symbol(undefined) + +foo4(undefined, 1); +>foo4 : Symbol(foo4, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 19, 1)) +>undefined : Symbol(undefined) + + +function removeUndefinedButNotFalse(x = true) { +>removeUndefinedButNotFalse : Symbol(removeUndefinedButNotFalse, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 31, 19)) +>x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 34, 36)) + + if (x === false) { +>x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 34, 36)) + + return x; +>x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 34, 36)) + } +} + +declare const cond: boolean; +>cond : Symbol(cond, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 40, 13)) + +function removeNothing(y = cond ? true : undefined) { +>removeNothing : Symbol(removeNothing, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 40, 28)) +>y : Symbol(y, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 41, 23)) +>cond : Symbol(cond, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 40, 13)) +>undefined : Symbol(undefined) + + if (y !== undefined) { +>y : Symbol(y, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 41, 23)) +>undefined : Symbol(undefined) + + if (y === false) { +>y : Symbol(y, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 41, 23)) + + return y; +>y : Symbol(y, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 41, 23)) + } + } + return true; +} + diff --git a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.types b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.types new file mode 100644 index 0000000000..d95f925989 --- /dev/null +++ b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.types @@ -0,0 +1,180 @@ +=== tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts === +function f(addUndefined1 = "J", addUndefined2?: number) { +>f : (addUndefined1?: string, addUndefined2?: number | undefined) => number +>addUndefined1 : string +>"J" : "J" +>addUndefined2 : number | undefined + + return addUndefined1.length + (addUndefined2 || 0); +>addUndefined1.length + (addUndefined2 || 0) : number +>addUndefined1.length : number +>addUndefined1 : string +>length : number +>(addUndefined2 || 0) : number +>addUndefined2 || 0 : number +>addUndefined2 : number | undefined +>0 : 0 +} +function g(addUndefined = "J", addDefined: number) { +>g : (addUndefined: string | undefined, addDefined: number) => number +>addUndefined : string +>"J" : "J" +>addDefined : number + + return addUndefined.length + addDefined; +>addUndefined.length + addDefined : number +>addUndefined.length : number +>addUndefined : string +>length : number +>addDefined : number +} +let total = f() + f('a', 1) + f('b') + f(undefined, 2); +>total : number +>f() + f('a', 1) + f('b') + f(undefined, 2) : number +>f() + f('a', 1) + f('b') : number +>f() + f('a', 1) : number +>f() : number +>f : (addUndefined1?: string, addUndefined2?: number | undefined) => number +>f('a', 1) : number +>f : (addUndefined1?: string, addUndefined2?: number | undefined) => number +>'a' : "a" +>1 : 1 +>f('b') : number +>f : (addUndefined1?: string, addUndefined2?: number | undefined) => number +>'b' : "b" +>f(undefined, 2) : number +>f : (addUndefined1?: string, addUndefined2?: number | undefined) => number +>undefined : undefined +>2 : 2 + +total = g('c', 3) + g(undefined, 4); +>total = g('c', 3) + g(undefined, 4) : number +>total : number +>g('c', 3) + g(undefined, 4) : number +>g('c', 3) : number +>g : (addUndefined: string | undefined, addDefined: number) => number +>'c' : "c" +>3 : 3 +>g(undefined, 4) : number +>g : (addUndefined: string | undefined, addDefined: number) => number +>undefined : undefined +>4 : 4 + +function foo1(x: string = "string", b: number) { +>foo1 : (x: string | undefined, b: number) => void +>x : string +>"string" : "string" +>b : number + + x.length; +>x.length : number +>x : string +>length : number +} + +function foo2(x = "string", b: number) { +>foo2 : (x: string | undefined, b: number) => void +>x : string +>"string" : "string" +>b : number + + x.length; // ok, should be string +>x.length : number +>x : string +>length : number +} + +function foo3(x: string | undefined = "string", b: number) { +>foo3 : (x: string | undefined, b: number) => void +>x : string +>"string" : "string" +>b : number + + x.length; // ok, should be string +>x.length : number +>x : string +>length : number +} + +function foo4(x: string | undefined = undefined, b: number) { +>foo4 : (x: string | undefined, b: number) => void +>x : string | undefined +>undefined : undefined +>b : number + + x; // should be string | undefined +>x : string | undefined +} + + + +// .d.ts should have `string | undefined` for foo1, foo2, foo3 and foo4 +foo1(undefined, 1); +>foo1(undefined, 1) : void +>foo1 : (x: string | undefined, b: number) => void +>undefined : undefined +>1 : 1 + +foo2(undefined, 1); +>foo2(undefined, 1) : void +>foo2 : (x: string | undefined, b: number) => void +>undefined : undefined +>1 : 1 + +foo3(undefined, 1); +>foo3(undefined, 1) : void +>foo3 : (x: string | undefined, b: number) => void +>undefined : undefined +>1 : 1 + +foo4(undefined, 1); +>foo4(undefined, 1) : void +>foo4 : (x: string | undefined, b: number) => void +>undefined : undefined +>1 : 1 + + +function removeUndefinedButNotFalse(x = true) { +>removeUndefinedButNotFalse : (x?: boolean) => false | undefined +>x : boolean +>true : true + + if (x === false) { +>x === false : boolean +>x : boolean +>false : false + + return x; +>x : false + } +} + +declare const cond: boolean; +>cond : boolean + +function removeNothing(y = cond ? true : undefined) { +>removeNothing : (y?: boolean | undefined) => boolean +>y : boolean | undefined +>cond ? true : undefined : true | undefined +>cond : boolean +>true : true +>undefined : undefined + + if (y !== undefined) { +>y !== undefined : boolean +>y : boolean | undefined +>undefined : undefined + + if (y === false) { +>y === false : boolean +>y : boolean +>false : false + + return y; +>y : false + } + } + return true; +>true : true +} + diff --git a/tests/baselines/reference/discriminatedUnionErrorMessage.errors.txt b/tests/baselines/reference/discriminatedUnionErrorMessage.errors.txt new file mode 100644 index 0000000000..6f1eb511d3 --- /dev/null +++ b/tests/baselines/reference/discriminatedUnionErrorMessage.errors.txt @@ -0,0 +1,23 @@ +tests/cases/compiler/discriminatedUnionErrorMessage.ts(8,5): error TS2322: Type '{ kind: "sq"; x: number; y: number; }' is not assignable to type 'Shape'. + Type '{ kind: "sq"; x: number; y: number; }' is not assignable to type 'Square'. + Property 'size' is missing in type '{ kind: "sq"; x: number; y: number; }'. + + +==== tests/cases/compiler/discriminatedUnionErrorMessage.ts (1 errors) ==== + type Square = { kind: "sq", size: number } + type Rectangle = { kind: "rt", x: number, y: number } + type Circle = { kind: "cr", radius: number } + type Shape = + | Square + | Rectangle + | Circle; + let shape: Shape = { + ~~~~~ +!!! error TS2322: Type '{ kind: "sq"; x: number; y: number; }' is not assignable to type 'Shape'. +!!! error TS2322: Type '{ kind: "sq"; x: number; y: number; }' is not assignable to type 'Square'. +!!! error TS2322: Property 'size' is missing in type '{ kind: "sq"; x: number; y: number; }'. + kind: "sq", + x: 12, + y: 13, + } + \ No newline at end of file diff --git a/tests/baselines/reference/discriminatedUnionErrorMessage.js b/tests/baselines/reference/discriminatedUnionErrorMessage.js new file mode 100644 index 0000000000..10c94b19a4 --- /dev/null +++ b/tests/baselines/reference/discriminatedUnionErrorMessage.js @@ -0,0 +1,21 @@ +//// [discriminatedUnionErrorMessage.ts] +type Square = { kind: "sq", size: number } +type Rectangle = { kind: "rt", x: number, y: number } +type Circle = { kind: "cr", radius: number } +type Shape = + | Square + | Rectangle + | Circle; +let shape: Shape = { + kind: "sq", + x: 12, + y: 13, +} + + +//// [discriminatedUnionErrorMessage.js] +var shape = { + kind: "sq", + x: 12, + y: 13 +}; diff --git a/tests/baselines/reference/flowAfterFinally1.js b/tests/baselines/reference/flowAfterFinally1.js new file mode 100644 index 0000000000..93f3425d50 --- /dev/null +++ b/tests/baselines/reference/flowAfterFinally1.js @@ -0,0 +1,25 @@ +//// [flowAfterFinally1.ts] +declare function openFile(): void +declare function closeFile(): void +declare function someOperation(): {} + +var result: {} +openFile() +try { + result = someOperation() +} finally { + closeFile() +} + +result // should not error here + +//// [flowAfterFinally1.js] +var result; +openFile(); +try { + result = someOperation(); +} +finally { + closeFile(); +} +result; // should not error here diff --git a/tests/baselines/reference/flowAfterFinally1.symbols b/tests/baselines/reference/flowAfterFinally1.symbols new file mode 100644 index 0000000000..ad0ffbeb5c --- /dev/null +++ b/tests/baselines/reference/flowAfterFinally1.symbols @@ -0,0 +1,29 @@ +=== tests/cases/compiler/flowAfterFinally1.ts === +declare function openFile(): void +>openFile : Symbol(openFile, Decl(flowAfterFinally1.ts, 0, 0)) + +declare function closeFile(): void +>closeFile : Symbol(closeFile, Decl(flowAfterFinally1.ts, 0, 33)) + +declare function someOperation(): {} +>someOperation : Symbol(someOperation, Decl(flowAfterFinally1.ts, 1, 34)) + +var result: {} +>result : Symbol(result, Decl(flowAfterFinally1.ts, 4, 3)) + +openFile() +>openFile : Symbol(openFile, Decl(flowAfterFinally1.ts, 0, 0)) + +try { + result = someOperation() +>result : Symbol(result, Decl(flowAfterFinally1.ts, 4, 3)) +>someOperation : Symbol(someOperation, Decl(flowAfterFinally1.ts, 1, 34)) + +} finally { + closeFile() +>closeFile : Symbol(closeFile, Decl(flowAfterFinally1.ts, 0, 33)) +} + +result // should not error here +>result : Symbol(result, Decl(flowAfterFinally1.ts, 4, 3)) + diff --git a/tests/baselines/reference/flowAfterFinally1.types b/tests/baselines/reference/flowAfterFinally1.types new file mode 100644 index 0000000000..93aae4764b --- /dev/null +++ b/tests/baselines/reference/flowAfterFinally1.types @@ -0,0 +1,33 @@ +=== tests/cases/compiler/flowAfterFinally1.ts === +declare function openFile(): void +>openFile : () => void + +declare function closeFile(): void +>closeFile : () => void + +declare function someOperation(): {} +>someOperation : () => {} + +var result: {} +>result : {} + +openFile() +>openFile() : void +>openFile : () => void + +try { + result = someOperation() +>result = someOperation() : {} +>result : {} +>someOperation() : {} +>someOperation : () => {} + +} finally { + closeFile() +>closeFile() : void +>closeFile : () => void +} + +result // should not error here +>result : {} + diff --git a/tests/baselines/reference/optionalParameterRetainsNull.js b/tests/baselines/reference/optionalParameterRetainsNull.js new file mode 100644 index 0000000000..b8fb2c1235 --- /dev/null +++ b/tests/baselines/reference/optionalParameterRetainsNull.js @@ -0,0 +1,14 @@ +//// [optionalParameterRetainsNull.ts] +interface Bar { bar: number; foo: object | null; } + +let a = { + test (a: K, b?: Bar[K] | null) { } +}; +a.test("bar", null); // ok, null is assignable to number | null | undefined + + +//// [optionalParameterRetainsNull.js] +var a = { + test: function (a, b) { } +}; +a.test("bar", null); // ok, null is assignable to number | null | undefined diff --git a/tests/baselines/reference/optionalParameterRetainsNull.symbols b/tests/baselines/reference/optionalParameterRetainsNull.symbols new file mode 100644 index 0000000000..95786c1bdc --- /dev/null +++ b/tests/baselines/reference/optionalParameterRetainsNull.symbols @@ -0,0 +1,25 @@ +=== tests/cases/compiler/optionalParameterRetainsNull.ts === +interface Bar { bar: number; foo: object | null; } +>Bar : Symbol(Bar, Decl(optionalParameterRetainsNull.ts, 0, 0)) +>bar : Symbol(Bar.bar, Decl(optionalParameterRetainsNull.ts, 0, 15)) +>foo : Symbol(Bar.foo, Decl(optionalParameterRetainsNull.ts, 0, 29)) + +let a = { +>a : Symbol(a, Decl(optionalParameterRetainsNull.ts, 2, 3)) + + test (a: K, b?: Bar[K] | null) { } +>test : Symbol(test, Decl(optionalParameterRetainsNull.ts, 2, 9)) +>K : Symbol(K, Decl(optionalParameterRetainsNull.ts, 3, 7)) +>Bar : Symbol(Bar, Decl(optionalParameterRetainsNull.ts, 0, 0)) +>a : Symbol(a, Decl(optionalParameterRetainsNull.ts, 3, 29)) +>K : Symbol(K, Decl(optionalParameterRetainsNull.ts, 3, 7)) +>b : Symbol(b, Decl(optionalParameterRetainsNull.ts, 3, 34)) +>Bar : Symbol(Bar, Decl(optionalParameterRetainsNull.ts, 0, 0)) +>K : Symbol(K, Decl(optionalParameterRetainsNull.ts, 3, 7)) + +}; +a.test("bar", null); // ok, null is assignable to number | null | undefined +>a.test : Symbol(test, Decl(optionalParameterRetainsNull.ts, 2, 9)) +>a : Symbol(a, Decl(optionalParameterRetainsNull.ts, 2, 3)) +>test : Symbol(test, Decl(optionalParameterRetainsNull.ts, 2, 9)) + diff --git a/tests/baselines/reference/optionalParameterRetainsNull.types b/tests/baselines/reference/optionalParameterRetainsNull.types new file mode 100644 index 0000000000..30b7d61f76 --- /dev/null +++ b/tests/baselines/reference/optionalParameterRetainsNull.types @@ -0,0 +1,31 @@ +=== tests/cases/compiler/optionalParameterRetainsNull.ts === +interface Bar { bar: number; foo: object | null; } +>Bar : Bar +>bar : number +>foo : object | null +>null : null + +let a = { +>a : { test(a: K, b?: Bar[K] | null | undefined): void; } +>{ test (a: K, b?: Bar[K] | null) { }} : { test(a: K, b?: Bar[K] | null | undefined): void; } + + test (a: K, b?: Bar[K] | null) { } +>test : (a: K, b?: Bar[K] | null | undefined) => void +>K : K +>Bar : Bar +>a : K +>K : K +>b : Bar[K] | null | undefined +>Bar : Bar +>K : K +>null : null + +}; +a.test("bar", null); // ok, null is assignable to number | null | undefined +>a.test("bar", null) : void +>a.test : (a: K, b?: Bar[K] | null | undefined) => void +>a : { test(a: K, b?: Bar[K] | null | undefined): void; } +>test : (a: K, b?: Bar[K] | null | undefined) => void +>"bar" : "bar" +>null : null + diff --git a/tests/baselines/reference/packageJsonMain.js b/tests/baselines/reference/packageJsonMain.js index c7f63af27f..96509ab639 100644 --- a/tests/baselines/reference/packageJsonMain.js +++ b/tests/baselines/reference/packageJsonMain.js @@ -28,6 +28,7 @@ foo + bar + baz; //// [a.js] "use strict"; +exports.__esModule = true; var foo = require("foo"); var bar = require("bar"); var baz = require("baz"); diff --git a/tests/baselines/reference/packageJsonMain_isNonRecursive.js b/tests/baselines/reference/packageJsonMain_isNonRecursive.js index edd56b359e..97d7bbdf05 100644 --- a/tests/baselines/reference/packageJsonMain_isNonRecursive.js +++ b/tests/baselines/reference/packageJsonMain_isNonRecursive.js @@ -16,3 +16,4 @@ import foo = require("foo"); //// [a.js] "use strict"; +exports.__esModule = true; diff --git a/tests/baselines/reference/typingsLookup4.js b/tests/baselines/reference/typingsLookup4.js index b3bb2ed5de..d2424644d1 100644 --- a/tests/baselines/reference/typingsLookup4.js +++ b/tests/baselines/reference/typingsLookup4.js @@ -38,6 +38,7 @@ exports.__esModule = true; exports.l = 2; //// [index.js] "use strict"; +exports.__esModule = true; exports.m = 3; //// [a.js] "use strict"; diff --git a/tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts b/tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts new file mode 100644 index 0000000000..8abea19f6c --- /dev/null +++ b/tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts @@ -0,0 +1,51 @@ +// @strictNullChecks: true +// @declaration: true +function f(addUndefined1 = "J", addUndefined2?: number) { + return addUndefined1.length + (addUndefined2 || 0); +} +function g(addUndefined = "J", addDefined: number) { + return addUndefined.length + addDefined; +} +let total = f() + f('a', 1) + f('b') + f(undefined, 2); +total = g('c', 3) + g(undefined, 4); + +function foo1(x: string = "string", b: number) { + x.length; +} + +function foo2(x = "string", b: number) { + x.length; // ok, should be string +} + +function foo3(x: string | undefined = "string", b: number) { + x.length; // ok, should be string +} + +function foo4(x: string | undefined = undefined, b: number) { + x; // should be string | undefined +} + + + +// .d.ts should have `string | undefined` for foo1, foo2, foo3 and foo4 +foo1(undefined, 1); +foo2(undefined, 1); +foo3(undefined, 1); +foo4(undefined, 1); + + +function removeUndefinedButNotFalse(x = true) { + if (x === false) { + return x; + } +} + +declare const cond: boolean; +function removeNothing(y = cond ? true : undefined) { + if (y !== undefined) { + if (y === false) { + return y; + } + } + return true; +} diff --git a/tests/cases/compiler/discriminatedUnionErrorMessage.ts b/tests/cases/compiler/discriminatedUnionErrorMessage.ts new file mode 100644 index 0000000000..eb2b08e943 --- /dev/null +++ b/tests/cases/compiler/discriminatedUnionErrorMessage.ts @@ -0,0 +1,12 @@ +type Square = { kind: "sq", size: number } +type Rectangle = { kind: "rt", x: number, y: number } +type Circle = { kind: "cr", radius: number } +type Shape = + | Square + | Rectangle + | Circle; +let shape: Shape = { + kind: "sq", + x: 12, + y: 13, +} diff --git a/tests/cases/compiler/flowAfterFinally1.ts b/tests/cases/compiler/flowAfterFinally1.ts new file mode 100644 index 0000000000..74df6a1be2 --- /dev/null +++ b/tests/cases/compiler/flowAfterFinally1.ts @@ -0,0 +1,14 @@ +// @strictNullChecks: true +declare function openFile(): void +declare function closeFile(): void +declare function someOperation(): {} + +var result: {} +openFile() +try { + result = someOperation() +} finally { + closeFile() +} + +result // should not error here \ No newline at end of file diff --git a/tests/cases/compiler/optionalParameterRetainsNull.ts b/tests/cases/compiler/optionalParameterRetainsNull.ts new file mode 100644 index 0000000000..a3b50a82d2 --- /dev/null +++ b/tests/cases/compiler/optionalParameterRetainsNull.ts @@ -0,0 +1,7 @@ +// @strictNullChecks: true +interface Bar { bar: number; foo: object | null; } + +let a = { + test (a: K, b?: Bar[K] | null) { } +}; +a.test("bar", null); // ok, null is assignable to number | null | undefined diff --git a/tests/cases/fourslash/quickInfoDisplayPartsIife.ts b/tests/cases/fourslash/quickInfoDisplayPartsIife.ts new file mode 100644 index 0000000000..e4ce2bf96e --- /dev/null +++ b/tests/cases/fourslash/quickInfoDisplayPartsIife.ts @@ -0,0 +1,5 @@ +/// +// @strictNullChecks: true +////var iife = (function foo/*1*/(x, y) { return x })(12); + +verify.quickInfoAt('1', '(local function) foo(x: number, y?: undefined): number');