diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 17077c6798733..44543e0f3faf2 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10053,7 +10053,7 @@ namespace ts { } let type: FlowType; if (flow.flags & FlowFlags.AfterFinally) { - // block flow edge: finally -> pre-try (for larger explanation check comment in binder.ts - bindTryStatement + // 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; @@ -10221,7 +10221,7 @@ namespace ts { 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 + // 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; @@ -14320,12 +14320,20 @@ namespace ts { } function checkAssertion(node: AssertionExpression) { - const exprType = getRegularTypeOfObjectLiteral(getBaseTypeOfLiteralType(checkExpression(node.expression))); + const rawType = checkExpression(node.expression); checkSourceElement(node.type); const targetType = getTypeFromTypeNode(node.type); if (produceDiagnostics && targetType !== unknownType) { + const rawLiteralFlags = rawType.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral | TypeFlags.BooleanLiteral); + const check = (type: Type): boolean => !!(type.flags & rawLiteralFlags || + type.flags & TypeFlags.UnionOrIntersection && some((type).types, check)); + + const exprType = rawLiteralFlags && check(targetType) + ? rawType + : getRegularTypeOfObjectLiteral(getWidenedLiteralType(rawType)); + const widenedType = getWidenedType(exprType); if (!isTypeComparableTo(targetType, widenedType)) { checkTypeComparableTo(exprType, targetType, node, Diagnostics.Type_0_cannot_be_converted_to_type_1); diff --git a/tests/baselines/reference/literalAssertions.errors.txt b/tests/baselines/reference/literalAssertions.errors.txt new file mode 100644 index 0000000000000..0e62ae4480144 --- /dev/null +++ b/tests/baselines/reference/literalAssertions.errors.txt @@ -0,0 +1,44 @@ +tests/cases/conformance/expressions/typeAssertions/literalAssertions.ts(2,1): error TS2352: Type '0' cannot be converted to type '1'. +tests/cases/conformance/expressions/typeAssertions/literalAssertions.ts(5,1): error TS2352: Type 'false' cannot be converted to type 'string | true'. +tests/cases/conformance/expressions/typeAssertions/literalAssertions.ts(8,1): error TS2352: Type '"hello"' cannot be converted to type '"str"'. +tests/cases/conformance/expressions/typeAssertions/literalAssertions.ts(10,1): error TS2352: Type '"hello"' cannot be converted to type '"str" | 123'. +tests/cases/conformance/expressions/typeAssertions/literalAssertions.ts(15,1): error TS2352: Type '"hello"' cannot be converted to type '"str" & { _brand: any; }'. + Type '"hello"' is not comparable to type '"str"'. +tests/cases/conformance/expressions/typeAssertions/literalAssertions.ts(17,1): error TS2352: Type '"hello"' cannot be converted to type '1 | ("str" & { _brand: any; })'. + + +==== tests/cases/conformance/expressions/typeAssertions/literalAssertions.ts (6 errors) ==== + 0 as 0; // OK + 0 as 1; // Error + ~~~~~~ +!!! error TS2352: Type '0' cannot be converted to type '1'. + + false as (false | string); // OK + false as (true | string); // Error + ~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2352: Type 'false' cannot be converted to type 'string | true'. + false as (boolean | string); // OK + + 'hello' as 'str'; // Error + ~~~~~~~~~~~~~~~~ +!!! error TS2352: Type '"hello"' cannot be converted to type '"str"'. + 'hello' as 'hello'; // OK + 'hello' as ('str' | 123); // Error + ~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2352: Type '"hello"' cannot be converted to type '"str" | 123'. + 'hello' as ('hello' | 123); // OK + 'hello' as ('str' & 'hello'); // OK + 'hello' as ('str' | 'hello'); // OK + 'hello' as (1 | 2 | string); // OK + 'hello' as ('str' & { _brand: any }); // Error + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2352: Type '"hello"' cannot be converted to type '"str" & { _brand: any; }'. +!!! error TS2352: Type '"hello"' is not comparable to type '"str"'. + 'hello' as ('hello' & { _brand: any }); // OK + 'hello' as ('str' & { _brand: any } | 1); // Error + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2352: Type '"hello"' cannot be converted to type '1 | ("str" & { _brand: any; })'. + 'hello' as ('hello' & { _brand: any } | 1); // OK + + ('string' as string as 'literal'); // OK + \ No newline at end of file diff --git a/tests/baselines/reference/literalAssertions.js b/tests/baselines/reference/literalAssertions.js new file mode 100644 index 0000000000000..407d2dacee3dc --- /dev/null +++ b/tests/baselines/reference/literalAssertions.js @@ -0,0 +1,41 @@ +//// [literalAssertions.ts] +0 as 0; // OK +0 as 1; // Error + +false as (false | string); // OK +false as (true | string); // Error +false as (boolean | string); // OK + +'hello' as 'str'; // Error +'hello' as 'hello'; // OK +'hello' as ('str' | 123); // Error +'hello' as ('hello' | 123); // OK +'hello' as ('str' & 'hello'); // OK +'hello' as ('str' | 'hello'); // OK +'hello' as (1 | 2 | string); // OK +'hello' as ('str' & { _brand: any }); // Error +'hello' as ('hello' & { _brand: any }); // OK +'hello' as ('str' & { _brand: any } | 1); // Error +'hello' as ('hello' & { _brand: any } | 1); // OK + +('string' as string as 'literal'); // OK + + +//// [literalAssertions.js] +0; // OK +0; // Error +false; // OK +false; // Error +false; // OK +'hello'; // Error +'hello'; // OK +'hello'; // Error +'hello'; // OK +'hello'; // OK +'hello'; // OK +'hello'; // OK +'hello'; // Error +'hello'; // OK +'hello'; // Error +'hello'; // OK +'string'; // OK diff --git a/tests/baselines/reference/stringLiteralsAssertionsInEqualityComparisons02.errors.txt b/tests/baselines/reference/stringLiteralsAssertionsInEqualityComparisons02.errors.txt index fd92af4e84f63..5daeab5dbeddc 100644 --- a/tests/baselines/reference/stringLiteralsAssertionsInEqualityComparisons02.errors.txt +++ b/tests/baselines/reference/stringLiteralsAssertionsInEqualityComparisons02.errors.txt @@ -1,15 +1,21 @@ tests/cases/conformance/types/literal/stringLiteralsAssertionsInEqualityComparisons02.ts(3,9): error TS2365: Operator '===' cannot be applied to types '"foo"' and '"baz"'. +tests/cases/conformance/types/literal/stringLiteralsAssertionsInEqualityComparisons02.ts(3,19): error TS2352: Type '"bar"' cannot be converted to type '"baz"'. +tests/cases/conformance/types/literal/stringLiteralsAssertionsInEqualityComparisons02.ts(4,20): error TS2352: Type '"bar"' cannot be converted to type '"foo"'. tests/cases/conformance/types/literal/stringLiteralsAssertionsInEqualityComparisons02.ts(5,9): error TS2365: Operator '==' cannot be applied to types 'string' and 'number'. tests/cases/conformance/types/literal/stringLiteralsAssertionsInEqualityComparisons02.ts(5,19): error TS2352: Type 'string' cannot be converted to type 'number'. -==== tests/cases/conformance/types/literal/stringLiteralsAssertionsInEqualityComparisons02.ts (3 errors) ==== +==== tests/cases/conformance/types/literal/stringLiteralsAssertionsInEqualityComparisons02.ts (5 errors) ==== type EnhancedString = string & { enhancements: any }; var a = "foo" === "bar" as "baz"; ~~~~~~~~~~~~~~~~~~~~~~~~ !!! error TS2365: Operator '===' cannot be applied to types '"foo"' and '"baz"'. + ~~~~~~~~~~~~~~ +!!! error TS2352: Type '"bar"' cannot be converted to type '"baz"'. var b = "foo" !== ("bar" as "foo"); + ~~~~~~~~~~~~~~ +!!! error TS2352: Type '"bar"' cannot be converted to type '"foo"'. var c = "foo" == ("bar"); ~~~~~~~~~~~~~~~~~~~~~~~~ !!! error TS2365: Operator '==' cannot be applied to types 'string' and 'number'. diff --git a/tests/baselines/reference/stringLiteralsWithTypeAssertions01.errors.txt b/tests/baselines/reference/stringLiteralsWithTypeAssertions01.errors.txt new file mode 100644 index 0000000000000..d9d465c7afc24 --- /dev/null +++ b/tests/baselines/reference/stringLiteralsWithTypeAssertions01.errors.txt @@ -0,0 +1,25 @@ +tests/cases/conformance/types/literal/stringLiteralsWithTypeAssertions01.ts(3,9): error TS2352: Type '"foo"' cannot be converted to type '"bar"'. +tests/cases/conformance/types/literal/stringLiteralsWithTypeAssertions01.ts(4,9): error TS2352: Type '"bar"' cannot be converted to type '"foo"'. +tests/cases/conformance/types/literal/stringLiteralsWithTypeAssertions01.ts(7,9): error TS2352: Type '"foo" | "bar"' cannot be converted to type '"baz"'. + Type '"bar"' is not comparable to type '"baz"'. +tests/cases/conformance/types/literal/stringLiteralsWithTypeAssertions01.ts(8,9): error TS2352: Type '"baz"' cannot be converted to type '"foo" | "bar"'. + + +==== tests/cases/conformance/types/literal/stringLiteralsWithTypeAssertions01.ts (4 errors) ==== + let fooOrBar: "foo" | "bar"; + + let a = "foo" as "bar"; + ~~~~~~~~~~~~~~ +!!! error TS2352: Type '"foo"' cannot be converted to type '"bar"'. + let b = "bar" as "foo"; + ~~~~~~~~~~~~~~ +!!! error TS2352: Type '"bar"' cannot be converted to type '"foo"'. + let c = fooOrBar as "foo"; + let d = fooOrBar as "bar"; + let e = fooOrBar as "baz"; + ~~~~~~~~~~~~~~~~~ +!!! error TS2352: Type '"foo" | "bar"' cannot be converted to type '"baz"'. +!!! error TS2352: Type '"bar"' is not comparable to type '"baz"'. + let f = "baz" as typeof fooOrBar; + ~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2352: Type '"baz"' cannot be converted to type '"foo" | "bar"'. \ No newline at end of file diff --git a/tests/cases/conformance/expressions/typeAssertions/literalAssertions.ts b/tests/cases/conformance/expressions/typeAssertions/literalAssertions.ts new file mode 100644 index 0000000000000..db9740ea0620c --- /dev/null +++ b/tests/cases/conformance/expressions/typeAssertions/literalAssertions.ts @@ -0,0 +1,20 @@ +0 as 0; // OK +0 as 1; // Error + +false as (false | string); // OK +false as (true | string); // Error +false as (boolean | string); // OK + +'hello' as 'str'; // Error +'hello' as 'hello'; // OK +'hello' as ('str' | 123); // Error +'hello' as ('hello' | 123); // OK +'hello' as ('str' & 'hello'); // OK +'hello' as ('str' | 'hello'); // OK +'hello' as (1 | 2 | string); // OK +'hello' as ('str' & { _brand: any }); // Error +'hello' as ('hello' & { _brand: any }); // OK +'hello' as ('str' & { _brand: any } | 1); // Error +'hello' as ('hello' & { _brand: any } | 1); // OK + +('string' as string as 'literal'); // OK