Skip to content

Commit 349ae45

Browse files
authored
Reduce intersections with conflicting privates, elaborate on reasons (#37762)
* Elaborate on reasons for 'never' intersections * Accept new API baselines * Accept new baselines * Add tests * Accept new baselines * Address CR feedback
1 parent 2187ba1 commit 349ae45

20 files changed

+756
-209
lines changed

src/compiler/checker.ts

Lines changed: 44 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4148,7 +4148,9 @@ namespace ts {
41484148
return undefined!; // TODO: GH#18217
41494149
}
41504150

4151-
type = getReducedType(type);
4151+
if (!(context.flags & NodeBuilderFlags.NoTypeReduction)) {
4152+
type = getReducedType(type);
4153+
}
41524154

41534155
if (type.flags & TypeFlags.Any) {
41544156
context.approximateLength += 3;
@@ -8460,17 +8462,20 @@ namespace ts {
84608462
error(baseTypeNode.expression, Diagnostics.No_base_constructor_has_the_specified_number_of_type_arguments);
84618463
return type.resolvedBaseTypes = emptyArray;
84628464
}
8463-
baseType = getReducedType(getReturnTypeOfSignature(constructors[0]));
8465+
baseType = getReturnTypeOfSignature(constructors[0]);
84648466
}
84658467

84668468
if (baseType === errorType) {
84678469
return type.resolvedBaseTypes = emptyArray;
84688470
}
8469-
if (!isValidBaseType(baseType)) {
8470-
error(baseTypeNode.expression, Diagnostics.Base_constructor_return_type_0_is_not_an_object_type_or_intersection_of_object_types_with_statically_known_members, typeToString(baseType));
8471+
const reducedBaseType = getReducedType(baseType);
8472+
if (!isValidBaseType(reducedBaseType)) {
8473+
const elaboration = elaborateNeverIntersection(/*errorInfo*/ undefined, baseType);
8474+
const diagnostic = chainDiagnosticMessages(elaboration, Diagnostics.Base_constructor_return_type_0_is_not_an_object_type_or_intersection_of_object_types_with_statically_known_members, typeToString(reducedBaseType));
8475+
diagnostics.add(createDiagnosticForNodeFromMessageChain(baseTypeNode.expression, diagnostic));
84718476
return type.resolvedBaseTypes = emptyArray;
84728477
}
8473-
if (type === baseType || hasBaseType(baseType, type)) {
8478+
if (type === reducedBaseType || hasBaseType(reducedBaseType, type)) {
84748479
error(type.symbol.valueDeclaration, Diagnostics.Type_0_recursively_references_itself_as_a_base_type,
84758480
typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType));
84768481
return type.resolvedBaseTypes = emptyArray;
@@ -8482,7 +8487,7 @@ namespace ts {
84828487
// partial instantiation of the members without the base types fully resolved
84838488
type.members = undefined;
84848489
}
8485-
return type.resolvedBaseTypes = [baseType];
8490+
return type.resolvedBaseTypes = [reducedBaseType];
84868491
}
84878492

84888493
function areAllOuterTypeParametersApplied(type: Type): boolean { // TODO: GH#18217 Shouldn't this take an InterfaceType?
@@ -10457,7 +10462,7 @@ namespace ts {
1045710462
else if (type.flags & TypeFlags.Intersection) {
1045810463
if (!((<IntersectionType>type).objectFlags & ObjectFlags.IsNeverIntersectionComputed)) {
1045910464
(<IntersectionType>type).objectFlags |= ObjectFlags.IsNeverIntersectionComputed |
10460-
(some(getPropertiesOfUnionOrIntersectionType(<IntersectionType>type), isDiscriminantWithNeverType) ? ObjectFlags.IsNeverIntersection : 0);
10465+
(some(getPropertiesOfUnionOrIntersectionType(<IntersectionType>type), isNeverReducedProperty) ? ObjectFlags.IsNeverIntersection : 0);
1046110466
}
1046210467
return (<IntersectionType>type).objectFlags & ObjectFlags.IsNeverIntersection ? neverType : type;
1046310468
}
@@ -10476,12 +10481,39 @@ namespace ts {
1047610481
return reduced;
1047710482
}
1047810483

10484+
function isNeverReducedProperty(prop: Symbol) {
10485+
return isDiscriminantWithNeverType(prop) || isConflictingPrivateProperty(prop);
10486+
}
10487+
1047910488
function isDiscriminantWithNeverType(prop: Symbol) {
10489+
// Return true for a synthetic non-optional property with non-uniform types, where at least one is
10490+
// a literal type and none is never, that reduces to never.
1048010491
return !(prop.flags & SymbolFlags.Optional) &&
1048110492
(getCheckFlags(prop) & (CheckFlags.Discriminant | CheckFlags.HasNeverType)) === CheckFlags.Discriminant &&
1048210493
!!(getTypeOfSymbol(prop).flags & TypeFlags.Never);
1048310494
}
1048410495

10496+
function isConflictingPrivateProperty(prop: Symbol) {
10497+
// Return true for a synthetic property with multiple declarations, at least one of which is private.
10498+
return !prop.valueDeclaration && !!(getCheckFlags(prop) & CheckFlags.ContainsPrivate);
10499+
}
10500+
10501+
function elaborateNeverIntersection(errorInfo: DiagnosticMessageChain | undefined, type: Type) {
10502+
if (getObjectFlags(type) & ObjectFlags.IsNeverIntersection) {
10503+
const neverProp = find(getPropertiesOfUnionOrIntersectionType(<IntersectionType>type), isDiscriminantWithNeverType);
10504+
if (neverProp) {
10505+
return chainDiagnosticMessages(errorInfo, Diagnostics.The_intersection_0_was_reduced_to_never_because_property_1_has_conflicting_types_in_some_constituents,
10506+
typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.NoTypeReduction), symbolToString(neverProp));
10507+
}
10508+
const privateProp = find(getPropertiesOfUnionOrIntersectionType(<IntersectionType>type), isConflictingPrivateProperty);
10509+
if (privateProp) {
10510+
return chainDiagnosticMessages(errorInfo, Diagnostics.The_intersection_0_was_reduced_to_never_because_property_1_exists_in_multiple_constituents_and_is_private_in_some,
10511+
typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.NoTypeReduction), symbolToString(privateProp));
10512+
}
10513+
}
10514+
return errorInfo;
10515+
}
10516+
1048510517
/**
1048610518
* Return the symbol for the property with the given name in the given type. Creates synthetic union properties when
1048710519
* necessary, maps primitive types and type parameters are to their apparent types, and augments with properties from
@@ -15609,6 +15641,9 @@ namespace ts {
1560915641
return result;
1561015642
}
1561115643
}
15644+
else {
15645+
errorInfo = elaborateNeverIntersection(errorInfo, originalTarget);
15646+
}
1561215647
if (!headMessage && maybeSuppress) {
1561315648
lastSkippedInfo = [source, target];
1561415649
// Used by, eg, missing property checking to replace the top-level message with a more informative one
@@ -16490,14 +16525,7 @@ namespace ts {
1649016525
const sourcePropFlags = getDeclarationModifierFlagsFromSymbol(sourceProp);
1649116526
const targetPropFlags = getDeclarationModifierFlagsFromSymbol(targetProp);
1649216527
if (sourcePropFlags & ModifierFlags.Private || targetPropFlags & ModifierFlags.Private) {
16493-
const hasDifferingDeclarations = sourceProp.valueDeclaration !== targetProp.valueDeclaration;
16494-
if (getCheckFlags(sourceProp) & CheckFlags.ContainsPrivate && hasDifferingDeclarations) {
16495-
if (reportErrors) {
16496-
reportError(Diagnostics.Property_0_has_conflicting_declarations_and_is_inaccessible_in_type_1, symbolToString(sourceProp), typeToString(source));
16497-
}
16498-
return Ternary.False;
16499-
}
16500-
if (hasDifferingDeclarations) {
16528+
if (sourceProp.valueDeclaration !== targetProp.valueDeclaration) {
1650116529
if (reportErrors) {
1650216530
if (sourcePropFlags & ModifierFlags.Private && targetPropFlags & ModifierFlags.Private) {
1650316531
reportError(Diagnostics.Types_have_separate_declarations_of_a_private_property_0, symbolToString(targetProp));
@@ -23634,12 +23662,6 @@ namespace ts {
2363423662
const flags = getDeclarationModifierFlagsFromSymbol(prop);
2363523663
const errorNode = node.kind === SyntaxKind.QualifiedName ? node.right : node.kind === SyntaxKind.ImportType ? node : node.name;
2363623664

23637-
if (getCheckFlags(prop) & CheckFlags.ContainsPrivate) {
23638-
// Synthetic property with private constituent property
23639-
error(errorNode, Diagnostics.Property_0_has_conflicting_declarations_and_is_inaccessible_in_type_1, symbolToString(prop), typeToString(type));
23640-
return false;
23641-
}
23642-
2364323665
if (isSuper) {
2364423666
// TS 1.0 spec (April 2014): 4.8.2
2364523667
// - In a constructor, instance member function, instance member accessor, or
@@ -24135,7 +24157,7 @@ namespace ts {
2413524157
relatedInfo = suggestion.valueDeclaration && createDiagnosticForNode(suggestion.valueDeclaration, Diagnostics._0_is_declared_here, suggestedName);
2413624158
}
2413724159
else {
24138-
errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1, declarationNameToString(propNode), typeToString(containingType));
24160+
errorInfo = chainDiagnosticMessages(elaborateNeverIntersection(errorInfo, containingType), Diagnostics.Property_0_does_not_exist_on_type_1, declarationNameToString(propNode), typeToString(containingType));
2413924161
}
2414024162
}
2414124163
}

src/compiler/diagnosticMessages.json

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2145,10 +2145,6 @@
21452145
"category": "Error",
21462146
"code": 2545
21472147
},
2148-
"Property '{0}' has conflicting declarations and is inaccessible in type '{1}'.": {
2149-
"category": "Error",
2150-
"code": 2546
2151-
},
21522148
"The type returned by the '{0}()' method of an async iterator must be a promise for a type with a 'value' property.": {
21532149
"category": "Error",
21542150
"code": 2547
@@ -5733,5 +5729,13 @@
57335729
"An optional chain cannot contain private identifiers.": {
57345730
"category": "Error",
57355731
"code": 18030
5732+
},
5733+
"The intersection '{0}' was reduced to 'never' because property '{1}' has conflicting types in some constituents.": {
5734+
"category": "Error",
5735+
"code": 18031
5736+
},
5737+
"The intersection '{0}' was reduced to 'never' because property '{1}' exists in multiple constituents and is private in some.": {
5738+
"category": "Error",
5739+
"code": 18032
57365740
}
57375741
}

src/compiler/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3685,6 +3685,7 @@ namespace ts {
36853685
OmitParameterModifiers = 1 << 13, // Omit modifiers on parameters
36863686
UseAliasDefinedOutsideCurrentScope = 1 << 14, // Allow non-visible aliases
36873687
UseSingleQuotesForStringLiteralType = 1 << 28, // Use single quotes for string literal type
3688+
NoTypeReduction = 1 << 29, // Don't call getReducedType
36883689

36893690
// Error handling
36903691
AllowThisInObjectLiteral = 1 << 15,
@@ -3728,6 +3729,7 @@ namespace ts {
37283729

37293730
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.
37303731
UseSingleQuotesForStringLiteralType = 1 << 28, // Use single quotes for string literal type
3732+
NoTypeReduction = 1 << 29, // Don't call getReducedType
37313733

37323734
// Error Handling
37333735
AllowUniqueESSymbolType = 1 << 20, // This is bit 20 to align with the same bit in `NodeBuilderFlags`
@@ -3747,7 +3749,7 @@ namespace ts {
37473749
NodeBuilderFlagsMask = NoTruncation | WriteArrayAsGenericType | UseStructuralFallback | WriteTypeArgumentsOfSignature |
37483750
UseFullyQualifiedType | SuppressAnyReturnType | MultilineObjectLiterals | WriteClassExpressionAsTypeLiteral |
37493751
UseTypeOfFunction | OmitParameterModifiers | UseAliasDefinedOutsideCurrentScope | AllowUniqueESSymbolType | InTypeAlias |
3750-
UseSingleQuotesForStringLiteralType,
3752+
UseSingleQuotesForStringLiteralType | NoTypeReduction,
37513753
}
37523754

37533755
export const enum SymbolFormatFlags {

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2135,6 +2135,7 @@ declare namespace ts {
21352135
OmitParameterModifiers = 8192,
21362136
UseAliasDefinedOutsideCurrentScope = 16384,
21372137
UseSingleQuotesForStringLiteralType = 268435456,
2138+
NoTypeReduction = 536870912,
21382139
AllowThisInObjectLiteral = 32768,
21392140
AllowQualifedNameInPlaceOfIdentifier = 65536,
21402141
AllowAnonymousIdentifier = 131072,
@@ -2163,6 +2164,7 @@ declare namespace ts {
21632164
OmitParameterModifiers = 8192,
21642165
UseAliasDefinedOutsideCurrentScope = 16384,
21652166
UseSingleQuotesForStringLiteralType = 268435456,
2167+
NoTypeReduction = 536870912,
21662168
AllowUniqueESSymbolType = 1048576,
21672169
AddUndefined = 131072,
21682170
WriteArrowStyleSignature = 262144,
@@ -2171,7 +2173,7 @@ declare namespace ts {
21712173
InFirstTypeArgument = 4194304,
21722174
InTypeAlias = 8388608,
21732175
/** @deprecated */ WriteOwnNameForAnyLike = 0,
2174-
NodeBuilderFlagsMask = 277904747
2176+
NodeBuilderFlagsMask = 814775659
21752177
}
21762178
export enum SymbolFormatFlags {
21772179
None = 0,

tests/baselines/reference/api/typescript.d.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2135,6 +2135,7 @@ declare namespace ts {
21352135
OmitParameterModifiers = 8192,
21362136
UseAliasDefinedOutsideCurrentScope = 16384,
21372137
UseSingleQuotesForStringLiteralType = 268435456,
2138+
NoTypeReduction = 536870912,
21382139
AllowThisInObjectLiteral = 32768,
21392140
AllowQualifedNameInPlaceOfIdentifier = 65536,
21402141
AllowAnonymousIdentifier = 131072,
@@ -2163,6 +2164,7 @@ declare namespace ts {
21632164
OmitParameterModifiers = 8192,
21642165
UseAliasDefinedOutsideCurrentScope = 16384,
21652166
UseSingleQuotesForStringLiteralType = 268435456,
2167+
NoTypeReduction = 536870912,
21662168
AllowUniqueESSymbolType = 1048576,
21672169
AddUndefined = 131072,
21682170
WriteArrowStyleSignature = 262144,
@@ -2171,7 +2173,7 @@ declare namespace ts {
21712173
InFirstTypeArgument = 4194304,
21722174
InTypeAlias = 8388608,
21732175
/** @deprecated */ WriteOwnNameForAnyLike = 0,
2174-
NodeBuilderFlagsMask = 277904747
2176+
NodeBuilderFlagsMask = 814775659
21752177
}
21762178
export enum SymbolFormatFlags {
21772179
None = 0,

tests/baselines/reference/indexedAccessPrivateMemberOfGenericConstraint.errors.txt

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
tests/cases/compiler/indexedAccessPrivateMemberOfGenericConstraint.ts(9,24): error TS4105: Private or protected member 'a' cannot be accessed on a type parameter.
22
tests/cases/compiler/indexedAccessPrivateMemberOfGenericConstraint.ts(9,32): error TS4105: Private or protected member 'a' cannot be accessed on a type parameter.
33
tests/cases/compiler/indexedAccessPrivateMemberOfGenericConstraint.ts(10,27): error TS4105: Private or protected member 'a' cannot be accessed on a type parameter.
4-
tests/cases/compiler/indexedAccessPrivateMemberOfGenericConstraint.ts(11,27): error TS4105: Private or protected member 'a' cannot be accessed on a type parameter.
54

65

7-
==== tests/cases/compiler/indexedAccessPrivateMemberOfGenericConstraint.ts (4 errors) ====
6+
==== tests/cases/compiler/indexedAccessPrivateMemberOfGenericConstraint.ts (3 errors) ====
87
class A {
98
private a: number;
109
}
@@ -22,6 +21,4 @@ tests/cases/compiler/indexedAccessPrivateMemberOfGenericConstraint.ts(11,27): er
2221
~~~~~~
2322
!!! error TS4105: Private or protected member 'a' cannot be accessed on a type parameter.
2423
type Z<T extends A & B> = T["a"];
25-
~~~~~~
26-
!!! error TS4105: Private or protected member 'a' cannot be accessed on a type parameter.
2724

tests/baselines/reference/intersectionReduction.errors.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
tests/cases/conformance/types/intersection/intersectionReduction.ts(38,4): error TS2339: Property 'kind' does not exist on type 'never'.
2+
The intersection 'A & B' was reduced to 'never' because property 'kind' has conflicting types in some constituents.
23
tests/cases/conformance/types/intersection/intersectionReduction.ts(80,1): error TS2322: Type 'any' is not assignable to type 'never'.
34
tests/cases/conformance/types/intersection/intersectionReduction.ts(81,1): error TS2322: Type 'any' is not assignable to type 'never'.
45

@@ -44,6 +45,7 @@ tests/cases/conformance/types/intersection/intersectionReduction.ts(81,1): error
4445
ab.kind; // Error
4546
~~~~
4647
!!! error TS2339: Property 'kind' does not exist on type 'never'.
48+
!!! error TS2339: The intersection 'A & B' was reduced to 'never' because property 'kind' has conflicting types in some constituents.
4749

4850
declare let x: A | (B & C); // A
4951
let a: A = x;

tests/baselines/reference/intersectionReductionStrict.errors.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
tests/cases/conformance/types/intersection/intersectionReductionStrict.ts(38,4): error TS2339: Property 'kind' does not exist on type 'never'.
2+
The intersection 'A & B' was reduced to 'never' because property 'kind' has conflicting types in some constituents.
23
tests/cases/conformance/types/intersection/intersectionReductionStrict.ts(69,1): error TS2322: Type 'any' is not assignable to type 'never'.
34
tests/cases/conformance/types/intersection/intersectionReductionStrict.ts(70,1): error TS2322: Type 'any' is not assignable to type 'never'.
45

@@ -44,6 +45,7 @@ tests/cases/conformance/types/intersection/intersectionReductionStrict.ts(70,1):
4445
ab.kind; // Error
4546
~~~~
4647
!!! error TS2339: Property 'kind' does not exist on type 'never'.
48+
!!! error TS2339: The intersection 'A & B' was reduced to 'never' because property 'kind' has conflicting types in some constituents.
4749

4850
declare let x: A | (B & C); // A
4951
let a: A = x;
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
tests/cases/compiler/intersectionWithConflictingPrivates.ts(5,4): error TS2339: Property 'y' does not exist on type 'never'.
2+
The intersection 'A & B' was reduced to 'never' because property 'x' exists in multiple constituents and is private in some.
3+
tests/cases/compiler/intersectionWithConflictingPrivates.ts(6,1): error TS2322: Type '{}' is not assignable to type 'never'.
4+
The intersection 'A & B' was reduced to 'never' because property 'x' exists in multiple constituents and is private in some.
5+
6+
7+
==== tests/cases/compiler/intersectionWithConflictingPrivates.ts (2 errors) ====
8+
class A { private x: unknown; y?: string; }
9+
class B { private x: unknown; y?: string; }
10+
11+
declare let ab: A & B;
12+
ab.y = 'hello';
13+
~
14+
!!! error TS2339: Property 'y' does not exist on type 'never'.
15+
!!! error TS2339: The intersection 'A & B' was reduced to 'never' because property 'x' exists in multiple constituents and is private in some.
16+
ab = {};
17+
~~
18+
!!! error TS2322: Type '{}' is not assignable to type 'never'.
19+
!!! error TS2322: The intersection 'A & B' was reduced to 'never' because property 'x' exists in multiple constituents and is private in some.
20+
21+
function f1(node: A | B) {
22+
if (node instanceof A || node instanceof A) {
23+
node; // A
24+
}
25+
else {
26+
node; // B
27+
}
28+
node; // A | B
29+
}
30+
31+
// Repro from #37659
32+
33+
abstract class ViewNode { }
34+
abstract class ViewRefNode extends ViewNode { }
35+
abstract class ViewRefFileNode extends ViewRefNode { }
36+
37+
class CommitFileNode extends ViewRefFileNode {
38+
private _id: any;
39+
}
40+
41+
class ResultsFileNode extends ViewRefFileNode {
42+
private _id: any;
43+
}
44+
45+
class StashFileNode extends CommitFileNode {
46+
private _id2: any;
47+
}
48+
49+
class StatusFileNode extends ViewNode {
50+
private _id: any;
51+
}
52+
53+
class Foo {
54+
private async foo(node: CommitFileNode | ResultsFileNode | StashFileNode) {
55+
if (
56+
!(node instanceof CommitFileNode) &&
57+
!(node instanceof StashFileNode) &&
58+
!(node instanceof ResultsFileNode)
59+
) {
60+
return;
61+
}
62+
63+
await this.bar(node);
64+
}
65+
66+
private async bar(node: CommitFileNode | ResultsFileNode | StashFileNode | StatusFileNode, options?: {}) {
67+
return Promise.resolve(undefined);
68+
}
69+
}
70+

0 commit comments

Comments
 (0)