From be960fa35651abc83bef27f1a75e99ab40627330 Mon Sep 17 00:00:00 2001 From: Ozair Patel Date: Mon, 4 Nov 2019 16:53:31 -0600 Subject: [PATCH] Add related diagnostic to "used before defined" if type is a function that returns a union with undefined (#33171) * Add "use before defined" diagnostic * Make "use before defined" diagnostic as related information to TS2454 * Add baseline tests for "use before defined" * Add test for type alias union with undefined for "use before defined" diagnostic * Update baselines --- src/compiler/checker.ts | 21 ++++- src/compiler/diagnosticMessages.json | 4 + ...thUndefinedWithStrictNullChecks.errors.txt | 74 +++++++++++++++ ...sUnionWithUndefinedWithStrictNullChecks.js | 61 +++++++++++++ ...nWithUndefinedWithStrictNullChecks.symbols | 89 +++++++++++++++++++ ...ionWithUndefinedWithStrictNullChecks.types | 83 +++++++++++++++++ ...sUnionWithUndefinedWithStrictNullChecks.ts | 38 ++++++++ 7 files changed, 369 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.errors.txt create mode 100644 tests/baselines/reference/functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.js create mode 100644 tests/baselines/reference/functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.symbols create mode 100644 tests/baselines/reference/functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.types create mode 100644 tests/cases/compiler/functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ace3d5ee8cf..c05b64b2cc8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -20190,7 +20190,26 @@ namespace ts { } } else if (!assumeInitialized && !(getFalsyFlags(type) & TypeFlags.Undefined) && getFalsyFlags(flowType) & TypeFlags.Undefined) { - error(node, Diagnostics.Variable_0_is_used_before_being_assigned, symbolToString(symbol)); + const diag = error(node, Diagnostics.Variable_0_is_used_before_being_assigned, symbolToString(symbol)); + + // See GH:32846 - if the user is using a variable whose type is () => T1 | ... | undefined + // they may have meant to specify the type as (() => T1 | ...) | undefined + // This is assumed if: the type is a FunctionType, the return type is a Union, the last constituent of + // the union is `undefined` + if (type.symbol && type.symbol.declarations.length === 1 && isFunctionTypeNode(type.symbol.declarations[0])) { + const funcTypeNode = type.symbol.declarations[0]; + const returnType = getReturnTypeFromAnnotation(funcTypeNode); + if (returnType && returnType.flags & TypeFlags.Union) { + const unionTypes = (funcTypeNode.type).types; + if (unionTypes && unionTypes[unionTypes.length - 1].kind === SyntaxKind.UndefinedKeyword) { + const parenedFuncType = getMutableClone(funcTypeNode); + // Highlight to the end of the second to last constituent of the union + parenedFuncType.end = unionTypes[unionTypes.length - 2].end; + addRelatedInfo(diag, createDiagnosticForNode(parenedFuncType, Diagnostics.Did_you_mean_to_parenthesize_this_function_type)); + } + } + } + // Return the declared type to reduce follow-on errors return type; } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index dc5121fa304..eaf385d44eb 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1047,6 +1047,10 @@ "category": "Error", "code": 1359 }, + "Did you mean to parenthesize this function type?": { + "category": "Error", + "code": 1360 + }, "The types of '{0}' are incompatible between these types.": { "category": "Error", diff --git a/tests/baselines/reference/functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.errors.txt b/tests/baselines/reference/functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.errors.txt new file mode 100644 index 00000000000..7f55e5a6c94 --- /dev/null +++ b/tests/baselines/reference/functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.errors.txt @@ -0,0 +1,74 @@ +tests/cases/compiler/functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts(2,1): error TS2454: Variable 'a' is used before being assigned. +tests/cases/compiler/functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts(6,1): error TS2454: Variable 'c' is used before being assigned. +tests/cases/compiler/functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts(10,1): error TS2454: Variable 'd' is used before being assigned. +tests/cases/compiler/functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts(13,1): error TS2454: Variable 'e' is used before being assigned. +tests/cases/compiler/functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts(16,1): error TS2454: Variable 'f' is used before being assigned. +tests/cases/compiler/functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts(19,1): error TS2454: Variable 'g' is used before being assigned. +tests/cases/compiler/functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts(24,1): error TS2454: Variable 'h' is used before being assigned. +tests/cases/compiler/functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts(28,1): error TS2454: Variable 'i' is used before being assigned. +tests/cases/compiler/functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts(32,1): error TS2454: Variable 'j' is used before being assigned. +tests/cases/compiler/functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts(36,1): error TS2454: Variable 'j' is used before being assigned. + + +==== tests/cases/compiler/functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts (10 errors) ==== + let a: () => void | undefined; + a; // Error did you mean to paren this function type + ~ +!!! error TS2454: Variable 'a' is used before being assigned. +!!! related TS1360 tests/cases/compiler/functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts:1:8: Did you mean to parenthesize this function type? + + function b (): void | undefined {} + let c: typeof b; + c; + ~ +!!! error TS2454: Variable 'c' is used before being assigned. + + type Undefined = undefined + let d: () => void | Undefined; + d; + ~ +!!! error TS2454: Variable 'd' is used before being assigned. + + let e: () => string | void | number | object | undefined; + e; // Error did you mean to paren... + ~ +!!! error TS2454: Variable 'e' is used before being assigned. +!!! related TS1360 tests/cases/compiler/functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts:12:8: Did you mean to parenthesize this function type? + + let f: () => void; + f; + ~ +!!! error TS2454: Variable 'f' is used before being assigned. + + let g: () => undefined; + g; + ~ +!!! error TS2454: Variable 'g' is used before being assigned. + + type T1 = undefined | string; + type T2 = undefined | void; + let h: () => T1 & T2; + h; + ~ +!!! error TS2454: Variable 'h' is used before being assigned. + + type T3 = void | undefined; + let i: () => T3; + i; + ~ +!!! error TS2454: Variable 'i' is used before being assigned. + + type T4 = () => void | undefined; + let j: T4; + j; // Error did you mean to paren... + ~ +!!! error TS2454: Variable 'j' is used before being assigned. +!!! related TS1360 tests/cases/compiler/functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts:30:11: Did you mean to parenthesize this function type? + + type T5 = () => void + let k: T5 | undefined + j; + ~ +!!! error TS2454: Variable 'j' is used before being assigned. +!!! related TS1360 tests/cases/compiler/functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts:30:11: Did you mean to parenthesize this function type? + \ No newline at end of file diff --git a/tests/baselines/reference/functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.js b/tests/baselines/reference/functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.js new file mode 100644 index 00000000000..0b0876be959 --- /dev/null +++ b/tests/baselines/reference/functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.js @@ -0,0 +1,61 @@ +//// [functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts] +let a: () => void | undefined; +a; // Error did you mean to paren this function type + +function b (): void | undefined {} +let c: typeof b; +c; + +type Undefined = undefined +let d: () => void | Undefined; +d; + +let e: () => string | void | number | object | undefined; +e; // Error did you mean to paren... + +let f: () => void; +f; + +let g: () => undefined; +g; + +type T1 = undefined | string; +type T2 = undefined | void; +let h: () => T1 & T2; +h; + +type T3 = void | undefined; +let i: () => T3; +i; + +type T4 = () => void | undefined; +let j: T4; +j; // Error did you mean to paren... + +type T5 = () => void +let k: T5 | undefined +j; + + +//// [functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.js] +var a; +a; // Error did you mean to paren this function type +function b() { } +var c; +c; +var d; +d; +var e; +e; // Error did you mean to paren... +var f; +f; +var g; +g; +var h; +h; +var i; +i; +var j; +j; // Error did you mean to paren... +var k; +j; diff --git a/tests/baselines/reference/functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.symbols b/tests/baselines/reference/functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.symbols new file mode 100644 index 00000000000..373562b5359 --- /dev/null +++ b/tests/baselines/reference/functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.symbols @@ -0,0 +1,89 @@ +=== tests/cases/compiler/functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts === +let a: () => void | undefined; +>a : Symbol(a, Decl(functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts, 0, 3)) + +a; // Error did you mean to paren this function type +>a : Symbol(a, Decl(functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts, 0, 3)) + +function b (): void | undefined {} +>b : Symbol(b, Decl(functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts, 1, 2)) + +let c: typeof b; +>c : Symbol(c, Decl(functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts, 4, 3)) +>b : Symbol(b, Decl(functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts, 1, 2)) + +c; +>c : Symbol(c, Decl(functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts, 4, 3)) + +type Undefined = undefined +>Undefined : Symbol(Undefined, Decl(functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts, 5, 2)) + +let d: () => void | Undefined; +>d : Symbol(d, Decl(functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts, 8, 3)) +>Undefined : Symbol(Undefined, Decl(functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts, 5, 2)) + +d; +>d : Symbol(d, Decl(functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts, 8, 3)) + +let e: () => string | void | number | object | undefined; +>e : Symbol(e, Decl(functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts, 11, 3)) + +e; // Error did you mean to paren... +>e : Symbol(e, Decl(functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts, 11, 3)) + +let f: () => void; +>f : Symbol(f, Decl(functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts, 14, 3)) + +f; +>f : Symbol(f, Decl(functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts, 14, 3)) + +let g: () => undefined; +>g : Symbol(g, Decl(functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts, 17, 3)) + +g; +>g : Symbol(g, Decl(functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts, 17, 3)) + +type T1 = undefined | string; +>T1 : Symbol(T1, Decl(functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts, 18, 2)) + +type T2 = undefined | void; +>T2 : Symbol(T2, Decl(functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts, 20, 29)) + +let h: () => T1 & T2; +>h : Symbol(h, Decl(functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts, 22, 3)) +>T1 : Symbol(T1, Decl(functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts, 18, 2)) +>T2 : Symbol(T2, Decl(functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts, 20, 29)) + +h; +>h : Symbol(h, Decl(functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts, 22, 3)) + +type T3 = void | undefined; +>T3 : Symbol(T3, Decl(functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts, 23, 2)) + +let i: () => T3; +>i : Symbol(i, Decl(functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts, 26, 3)) +>T3 : Symbol(T3, Decl(functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts, 23, 2)) + +i; +>i : Symbol(i, Decl(functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts, 26, 3)) + +type T4 = () => void | undefined; +>T4 : Symbol(T4, Decl(functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts, 27, 2)) + +let j: T4; +>j : Symbol(j, Decl(functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts, 30, 3)) +>T4 : Symbol(T4, Decl(functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts, 27, 2)) + +j; // Error did you mean to paren... +>j : Symbol(j, Decl(functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts, 30, 3)) + +type T5 = () => void +>T5 : Symbol(T5, Decl(functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts, 31, 2)) + +let k: T5 | undefined +>k : Symbol(k, Decl(functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts, 34, 3)) +>T5 : Symbol(T5, Decl(functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts, 31, 2)) + +j; +>j : Symbol(j, Decl(functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts, 30, 3)) + diff --git a/tests/baselines/reference/functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.types b/tests/baselines/reference/functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.types new file mode 100644 index 00000000000..2e40dced0d1 --- /dev/null +++ b/tests/baselines/reference/functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.types @@ -0,0 +1,83 @@ +=== tests/cases/compiler/functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts === +let a: () => void | undefined; +>a : () => void | undefined + +a; // Error did you mean to paren this function type +>a : () => void | undefined + +function b (): void | undefined {} +>b : () => void | undefined + +let c: typeof b; +>c : () => void | undefined +>b : () => void | undefined + +c; +>c : () => void | undefined + +type Undefined = undefined +>Undefined : undefined + +let d: () => void | Undefined; +>d : () => void | undefined + +d; +>d : () => void | undefined + +let e: () => string | void | number | object | undefined; +>e : () => string | number | void | object | undefined + +e; // Error did you mean to paren... +>e : () => string | number | void | object | undefined + +let f: () => void; +>f : () => void + +f; +>f : () => void + +let g: () => undefined; +>g : () => undefined + +g; +>g : () => undefined + +type T1 = undefined | string; +>T1 : T1 + +type T2 = undefined | void; +>T2 : void | undefined + +let h: () => T1 & T2; +>h : () => undefined + +h; +>h : () => undefined + +type T3 = void | undefined; +>T3 : void | undefined + +let i: () => T3; +>i : () => void | undefined + +i; +>i : () => void | undefined + +type T4 = () => void | undefined; +>T4 : T4 + +let j: T4; +>j : T4 + +j; // Error did you mean to paren... +>j : T4 + +type T5 = () => void +>T5 : T5 + +let k: T5 | undefined +>k : T5 | undefined + +j; +>j : T4 + diff --git a/tests/cases/compiler/functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts b/tests/cases/compiler/functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts new file mode 100644 index 00000000000..95750758e24 --- /dev/null +++ b/tests/cases/compiler/functionTypeReturnsUnionWithUndefinedWithStrictNullChecks.ts @@ -0,0 +1,38 @@ +// @strictNullChecks: true + +let a: () => void | undefined; +a; // Error did you mean to paren this function type + +function b (): void | undefined {} +let c: typeof b; +c; + +type Undefined = undefined +let d: () => void | Undefined; +d; + +let e: () => string | void | number | object | undefined; +e; // Error did you mean to paren... + +let f: () => void; +f; + +let g: () => undefined; +g; + +type T1 = undefined | string; +type T2 = undefined | void; +let h: () => T1 & T2; +h; + +type T3 = void | undefined; +let i: () => T3; +i; + +type T4 = () => void | undefined; +let j: T4; +j; // Error did you mean to paren... + +type T5 = () => void +let k: T5 | undefined +j;