Skip to content

Better assignability errors to never-producing vacuous intersections #37618

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4102,7 +4102,9 @@ namespace ts {
return undefined!; // TODO: GH#18217
}

type = getReducedType(type);
if (!(context.flags & NodeBuilderFlags.PreserveVacuousIntersections)) {
type = getReducedType(type);
}

if (type.flags & TypeFlags.Any) {
context.approximateLength += 3;
Expand Down Expand Up @@ -15528,6 +15530,10 @@ namespace ts {
return result;
}
}
else if (getObjectFlags(originalTarget) & ObjectFlags.IsNeverIntersection) {
const intersectionString = typeToString(originalTarget, /*enclosingDeclaration*/ undefined, TypeFormatFlags.PreserveVacuousIntersections);
errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.The_type_never_was_reduced_from_the_intersection_0_Each_type_of_that_intersection_has_properties_that_conflict_so_values_of_that_type_can_never_exist, intersectionString);
}
if (!headMessage && maybeSuppress) {
lastSkippedInfo = [source, target];
// Used by, eg, missing property checking to replace the top-level message with a more informative one
Expand Down Expand Up @@ -24098,6 +24104,10 @@ namespace ts {
relatedInfo = suggestion.valueDeclaration && createDiagnosticForNode(suggestion.valueDeclaration, Diagnostics._0_is_declared_here, suggestedName);
}
else {
if (getObjectFlags(containingType) & ObjectFlags.IsNeverIntersection) {
const intersectionString = typeToString(containingType, /*enclosingDeclaration*/ undefined, TypeFormatFlags.PreserveVacuousIntersections);
errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.The_type_never_was_reduced_from_the_intersection_0_Each_type_of_that_intersection_has_properties_that_conflict_so_values_of_that_type_can_never_exist, intersectionString);
}
errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1, declarationNameToString(propNode), typeToString(containingType));
}
}
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -5653,5 +5653,9 @@
"An optional chain cannot contain private identifiers.": {
"category": "Error",
"code": 18030
},
"The type 'never' was reduced from the intersection '{0}'. Each type of that intersection has properties that conflict, so values of that type can never exist.": {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why 'each' here? Wouldn't 'some' be correct?

If that's true, I'd just say "Types in that intersection ..."

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point

"category": "Error",
"code": 18031
}
}
4 changes: 3 additions & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3687,6 +3687,7 @@ namespace ts {
OmitParameterModifiers = 1 << 13, // Omit modifiers on parameters
UseAliasDefinedOutsideCurrentScope = 1 << 14, // Allow non-visible aliases
UseSingleQuotesForStringLiteralType = 1 << 28, // Use single quotes for string literal type
PreserveVacuousIntersections = 1 << 29, // uh oh...

// Error handling
AllowThisInObjectLiteral = 1 << 15,
Expand Down Expand Up @@ -3730,6 +3731,7 @@ namespace ts {

UseAliasDefinedOutsideCurrentScope = 1 << 14, // For a `type T = ... ` defined in a different file, write `T` instead of its value, even though `T` can't be accessed in the current scope.
UseSingleQuotesForStringLiteralType = 1 << 28, // Use single quotes for string literal type
PreserveVacuousIntersections = 1 << 29, // uh oh

// Error Handling
AllowUniqueESSymbolType = 1 << 20, // This is bit 20 to align with the same bit in `NodeBuilderFlags`
Expand All @@ -3749,7 +3751,7 @@ namespace ts {
NodeBuilderFlagsMask = NoTruncation | WriteArrayAsGenericType | UseStructuralFallback | WriteTypeArgumentsOfSignature |
UseFullyQualifiedType | SuppressAnyReturnType | MultilineObjectLiterals | WriteClassExpressionAsTypeLiteral |
UseTypeOfFunction | OmitParameterModifiers | UseAliasDefinedOutsideCurrentScope | AllowUniqueESSymbolType | InTypeAlias |
UseSingleQuotesForStringLiteralType,
UseSingleQuotesForStringLiteralType | PreserveVacuousIntersections,
}

export const enum SymbolFormatFlags {
Expand Down
4 changes: 3 additions & 1 deletion tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2136,6 +2136,7 @@ declare namespace ts {
OmitParameterModifiers = 8192,
UseAliasDefinedOutsideCurrentScope = 16384,
UseSingleQuotesForStringLiteralType = 268435456,
PreserveVacuousIntersections = 536870912,
AllowThisInObjectLiteral = 32768,
AllowQualifedNameInPlaceOfIdentifier = 65536,
AllowAnonymousIdentifier = 131072,
Expand Down Expand Up @@ -2164,6 +2165,7 @@ declare namespace ts {
OmitParameterModifiers = 8192,
UseAliasDefinedOutsideCurrentScope = 16384,
UseSingleQuotesForStringLiteralType = 268435456,
PreserveVacuousIntersections = 536870912,
AllowUniqueESSymbolType = 1048576,
AddUndefined = 131072,
WriteArrowStyleSignature = 262144,
Expand All @@ -2172,7 +2174,7 @@ declare namespace ts {
InFirstTypeArgument = 4194304,
InTypeAlias = 8388608,
/** @deprecated */ WriteOwnNameForAnyLike = 0,
NodeBuilderFlagsMask = 277904747
NodeBuilderFlagsMask = 814775659
}
export enum SymbolFormatFlags {
None = 0,
Expand Down
4 changes: 3 additions & 1 deletion tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2136,6 +2136,7 @@ declare namespace ts {
OmitParameterModifiers = 8192,
UseAliasDefinedOutsideCurrentScope = 16384,
UseSingleQuotesForStringLiteralType = 268435456,
PreserveVacuousIntersections = 536870912,
AllowThisInObjectLiteral = 32768,
AllowQualifedNameInPlaceOfIdentifier = 65536,
AllowAnonymousIdentifier = 131072,
Expand Down Expand Up @@ -2164,6 +2165,7 @@ declare namespace ts {
OmitParameterModifiers = 8192,
UseAliasDefinedOutsideCurrentScope = 16384,
UseSingleQuotesForStringLiteralType = 268435456,
PreserveVacuousIntersections = 536870912,
AllowUniqueESSymbolType = 1048576,
AddUndefined = 131072,
WriteArrowStyleSignature = 262144,
Expand All @@ -2172,7 +2174,7 @@ declare namespace ts {
InFirstTypeArgument = 4194304,
InTypeAlias = 8388608,
/** @deprecated */ WriteOwnNameForAnyLike = 0,
NodeBuilderFlagsMask = 277904747
NodeBuilderFlagsMask = 814775659
}
export enum SymbolFormatFlags {
None = 0,
Expand Down
12 changes: 10 additions & 2 deletions tests/baselines/reference/intersectionReduction.errors.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
tests/cases/conformance/types/intersection/intersectionReduction.ts(38,4): error TS2339: Property 'kind' does not exist on type 'never'.
tests/cases/conformance/types/intersection/intersectionReduction.ts(80,1): error TS2322: Type 'any' is not assignable to type 'never'.
The type 'never' was reduced from the intersection 'A & B'. Each type of that intersection has properties that conflict, so values of that type can never exist.
tests/cases/conformance/types/intersection/intersectionReduction.ts(39,1): error TS2322: Type '42' is not assignable to type 'never'.
The type 'never' was reduced from the intersection 'A & B'. Each type of that intersection has properties that conflict, so values of that type can never exist.
tests/cases/conformance/types/intersection/intersectionReduction.ts(81,1): error TS2322: Type 'any' is not assignable to type 'never'.
tests/cases/conformance/types/intersection/intersectionReduction.ts(82,1): error TS2322: Type 'any' is not assignable to type 'never'.


==== tests/cases/conformance/types/intersection/intersectionReduction.ts (3 errors) ====
==== tests/cases/conformance/types/intersection/intersectionReduction.ts (4 errors) ====
declare const sym1: unique symbol;
declare const sym2: unique symbol;

Expand Down Expand Up @@ -44,6 +47,11 @@ tests/cases/conformance/types/intersection/intersectionReduction.ts(81,1): error
ab.kind; // Error
~~~~
!!! error TS2339: Property 'kind' does not exist on type 'never'.
!!! error TS2339: The type 'never' was reduced from the intersection 'A & B'. Each type of that intersection has properties that conflict, so values of that type can never exist.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should probably search for and report the properties that conflict and their respective types, no? So the error is somewhat more actionable?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely seems doable, but only if the approach in this PR seems reasonable.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems OK. We could probably consider leaving behind more "breadcrumbs" like this in the future to explain how a specific type came about; but it's always about striking that balance between useful explaianation and terseness.

ab = 42;
~~
!!! error TS2322: Type '42' is not assignable to type 'never'.
!!! error TS2322: The type 'never' was reduced from the intersection 'A & B'. Each type of that intersection has properties that conflict, so values of that type can never exist.

declare let x: A | (B & C); // A
let a: A = x;
Expand Down
2 changes: 2 additions & 0 deletions tests/baselines/reference/intersectionReduction.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type C = { kind: 'c', foo: number };

declare let ab: A & B;
ab.kind; // Error
ab = 42;

declare let x: A | (B & C); // A
let a: A = x;
Expand Down Expand Up @@ -111,6 +112,7 @@ const f4 = (t: number | (Container<"b"> & { dataB: boolean } & Container<"a">)):

//// [intersectionReduction.js]
ab.kind; // Error
ab = 42;
var a = x;
var r1 = f10(a1); // unknown
var r2 = f10(a2); // string
Expand Down
Loading