Skip to content
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

Reduce intersections with conflicting privates, elaborate on reasons #37762

Merged
merged 7 commits into from
Apr 3, 2020
Merged
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
66 changes: 44 additions & 22 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4148,7 +4148,9 @@ namespace ts {
return undefined!; // TODO: GH#18217
}

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

if (type.flags & TypeFlags.Any) {
context.approximateLength += 3;
Expand Down Expand Up @@ -8460,17 +8462,20 @@ namespace ts {
error(baseTypeNode.expression, Diagnostics.No_base_constructor_has_the_specified_number_of_type_arguments);
return type.resolvedBaseTypes = emptyArray;
}
baseType = getReducedType(getReturnTypeOfSignature(constructors[0]));
baseType = getReturnTypeOfSignature(constructors[0]);
}

if (baseType === errorType) {
return type.resolvedBaseTypes = emptyArray;
}
if (!isValidBaseType(baseType)) {
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));
const reducedBaseType = getReducedType(baseType);
if (!isValidBaseType(reducedBaseType)) {
const elaboration = elaborateNeverIntersection(/*errorInfo*/ undefined, baseType);
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));
diagnostics.add(createDiagnosticForNodeFromMessageChain(baseTypeNode.expression, diagnostic));
return type.resolvedBaseTypes = emptyArray;
}
if (type === baseType || hasBaseType(baseType, type)) {
if (type === reducedBaseType || hasBaseType(reducedBaseType, type)) {
error(type.symbol.valueDeclaration, Diagnostics.Type_0_recursively_references_itself_as_a_base_type,
typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType));
return type.resolvedBaseTypes = emptyArray;
Expand All @@ -8482,7 +8487,7 @@ namespace ts {
// partial instantiation of the members without the base types fully resolved
type.members = undefined;
}
return type.resolvedBaseTypes = [baseType];
return type.resolvedBaseTypes = [reducedBaseType];
}

function areAllOuterTypeParametersApplied(type: Type): boolean { // TODO: GH#18217 Shouldn't this take an InterfaceType?
Expand Down Expand Up @@ -10457,7 +10462,7 @@ namespace ts {
else if (type.flags & TypeFlags.Intersection) {
if (!((<IntersectionType>type).objectFlags & ObjectFlags.IsNeverIntersectionComputed)) {
(<IntersectionType>type).objectFlags |= ObjectFlags.IsNeverIntersectionComputed |
(some(getPropertiesOfUnionOrIntersectionType(<IntersectionType>type), isDiscriminantWithNeverType) ? ObjectFlags.IsNeverIntersection : 0);
(some(getPropertiesOfUnionOrIntersectionType(<IntersectionType>type), isNeverReducedProperty) ? ObjectFlags.IsNeverIntersection : 0);
}
return (<IntersectionType>type).objectFlags & ObjectFlags.IsNeverIntersection ? neverType : type;
}
Expand All @@ -10476,12 +10481,39 @@ namespace ts {
return reduced;
}

function isNeverReducedProperty(prop: Symbol) {
return isDiscriminantWithNeverType(prop) || isConflictingPrivateProperty(prop);
}

function isDiscriminantWithNeverType(prop: Symbol) {
// Return true for a synthetic non-optional property with non-uniform types, where at least one is
// a literal type and none is never, that reduces to never.
return !(prop.flags & SymbolFlags.Optional) &&
(getCheckFlags(prop) & (CheckFlags.Discriminant | CheckFlags.HasNeverType)) === CheckFlags.Discriminant &&
!!(getTypeOfSymbol(prop).flags & TypeFlags.Never);
}

function isConflictingPrivateProperty(prop: Symbol) {
// Return true for a synthetic property with multiple declarations, at least one of which is private.
return !prop.valueDeclaration && !!(getCheckFlags(prop) & CheckFlags.ContainsPrivate);
}

function elaborateNeverIntersection(errorInfo: DiagnosticMessageChain | undefined, type: Type) {
if (getObjectFlags(type) & ObjectFlags.IsNeverIntersection) {
const neverProp = find(getPropertiesOfUnionOrIntersectionType(<IntersectionType>type), isDiscriminantWithNeverType);
if (neverProp) {
return chainDiagnosticMessages(errorInfo, Diagnostics.The_intersection_0_was_reduced_to_never_because_property_1_has_conflicting_types_in_some_constituents,
typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.NoTypeReduction), symbolToString(neverProp));
}
const privateProp = find(getPropertiesOfUnionOrIntersectionType(<IntersectionType>type), isConflictingPrivateProperty);
if (privateProp) {
Copy link
Member

Choose a reason for hiding this comment

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

Should we add related spans pointing at the location(s) of the private declarations? I imagine one of the possible "fixes" for this error is just making the fields public.

Copy link
Member Author

Choose a reason for hiding this comment

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

We could consider it, but not super high value given that this is a pretty esoteric error.

return chainDiagnosticMessages(errorInfo, Diagnostics.The_intersection_0_was_reduced_to_never_because_property_1_exists_in_multiple_constituents_and_is_private_in_some,
typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.NoTypeReduction), symbolToString(privateProp));
}
}
return errorInfo;
}

/**
* Return the symbol for the property with the given name in the given type. Creates synthetic union properties when
* necessary, maps primitive types and type parameters are to their apparent types, and augments with properties from
Expand Down Expand Up @@ -15609,6 +15641,9 @@ namespace ts {
return result;
}
Copy link
Member

Choose a reason for hiding this comment

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

You can actually just write

Suggested change
else if (target.flags & TypeFlags.Never && originalTarget.flags & TypeFlags.Intersection) {
else if (getObjectFlags(originalTarget) & ObjectFlags.IsNeverIntersection) {

You can also move the check itself into a function that tries to elaborate on the current errorInfo and do an unconditional assignment (implementation was posted in one of my earlier comments.

}
else {
errorInfo = elaborateNeverIntersection(errorInfo, originalTarget);
}
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 @@ -16490,14 +16525,7 @@ namespace ts {
const sourcePropFlags = getDeclarationModifierFlagsFromSymbol(sourceProp);
const targetPropFlags = getDeclarationModifierFlagsFromSymbol(targetProp);
if (sourcePropFlags & ModifierFlags.Private || targetPropFlags & ModifierFlags.Private) {
const hasDifferingDeclarations = sourceProp.valueDeclaration !== targetProp.valueDeclaration;
if (getCheckFlags(sourceProp) & CheckFlags.ContainsPrivate && hasDifferingDeclarations) {
if (reportErrors) {
reportError(Diagnostics.Property_0_has_conflicting_declarations_and_is_inaccessible_in_type_1, symbolToString(sourceProp), typeToString(source));
}
return Ternary.False;
}
if (hasDifferingDeclarations) {
if (sourceProp.valueDeclaration !== targetProp.valueDeclaration) {
if (reportErrors) {
if (sourcePropFlags & ModifierFlags.Private && targetPropFlags & ModifierFlags.Private) {
reportError(Diagnostics.Types_have_separate_declarations_of_a_private_property_0, symbolToString(targetProp));
Expand Down Expand Up @@ -23633,12 +23661,6 @@ namespace ts {
const flags = getDeclarationModifierFlagsFromSymbol(prop);
const errorNode = node.kind === SyntaxKind.QualifiedName ? node.right : node.kind === SyntaxKind.ImportType ? node : node.name;

if (getCheckFlags(prop) & CheckFlags.ContainsPrivate) {
// Synthetic property with private constituent property
error(errorNode, Diagnostics.Property_0_has_conflicting_declarations_and_is_inaccessible_in_type_1, symbolToString(prop), typeToString(type));
return false;
}

if (isSuper) {
// TS 1.0 spec (April 2014): 4.8.2
// - In a constructor, instance member function, instance member accessor, or
Expand Down Expand Up @@ -24134,7 +24156,7 @@ namespace ts {
relatedInfo = suggestion.valueDeclaration && createDiagnosticForNode(suggestion.valueDeclaration, Diagnostics._0_is_declared_here, suggestedName);
}
else {
errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1, declarationNameToString(propNode), typeToString(containingType));
errorInfo = chainDiagnosticMessages(elaborateNeverIntersection(errorInfo, containingType), Diagnostics.Property_0_does_not_exist_on_type_1, declarationNameToString(propNode), typeToString(containingType));
}
}
}
Expand Down
12 changes: 8 additions & 4 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -2145,10 +2145,6 @@
"category": "Error",
"code": 2545
},
"Property '{0}' has conflicting declarations and is inaccessible in type '{1}'.": {
"category": "Error",
"code": 2546
},
"The type returned by the '{0}()' method of an async iterator must be a promise for a type with a 'value' property.": {
"category": "Error",
"code": 2547
Expand Down Expand Up @@ -5733,5 +5729,13 @@
"An optional chain cannot contain private identifiers.": {
"category": "Error",
"code": 18030
},
"The intersection '{0}' was reduced to 'never' because property '{1}' has conflicting types in some constituents.": {
"category": "Error",
"code": 18031
},
"The intersection '{0}' was reduced to 'never' because property '{1}' exists in multiple constituents and is private in some.": {
"category": "Error",
"code": 18032
}
}
4 changes: 3 additions & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3685,6 +3685,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
NoTypeReduction = 1 << 29, // Don't call getReducedType

// Error handling
AllowThisInObjectLiteral = 1 << 15,
Expand Down Expand Up @@ -3728,6 +3729,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
NoTypeReduction = 1 << 29, // Don't call getReducedType

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

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 @@ -2135,6 +2135,7 @@ declare namespace ts {
OmitParameterModifiers = 8192,
UseAliasDefinedOutsideCurrentScope = 16384,
UseSingleQuotesForStringLiteralType = 268435456,
NoTypeReduction = 536870912,
AllowThisInObjectLiteral = 32768,
AllowQualifedNameInPlaceOfIdentifier = 65536,
AllowAnonymousIdentifier = 131072,
Expand Down Expand Up @@ -2163,6 +2164,7 @@ declare namespace ts {
OmitParameterModifiers = 8192,
UseAliasDefinedOutsideCurrentScope = 16384,
UseSingleQuotesForStringLiteralType = 268435456,
NoTypeReduction = 536870912,
AllowUniqueESSymbolType = 1048576,
AddUndefined = 131072,
WriteArrowStyleSignature = 262144,
Expand All @@ -2171,7 +2173,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 @@ -2135,6 +2135,7 @@ declare namespace ts {
OmitParameterModifiers = 8192,
UseAliasDefinedOutsideCurrentScope = 16384,
UseSingleQuotesForStringLiteralType = 268435456,
NoTypeReduction = 536870912,
AllowThisInObjectLiteral = 32768,
AllowQualifedNameInPlaceOfIdentifier = 65536,
AllowAnonymousIdentifier = 131072,
Expand Down Expand Up @@ -2163,6 +2164,7 @@ declare namespace ts {
OmitParameterModifiers = 8192,
UseAliasDefinedOutsideCurrentScope = 16384,
UseSingleQuotesForStringLiteralType = 268435456,
NoTypeReduction = 536870912,
AllowUniqueESSymbolType = 1048576,
AddUndefined = 131072,
WriteArrowStyleSignature = 262144,
Expand All @@ -2171,7 +2173,7 @@ declare namespace ts {
InFirstTypeArgument = 4194304,
InTypeAlias = 8388608,
/** @deprecated */ WriteOwnNameForAnyLike = 0,
NodeBuilderFlagsMask = 277904747
NodeBuilderFlagsMask = 814775659
}
export enum SymbolFormatFlags {
None = 0,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
tests/cases/compiler/indexedAccessPrivateMemberOfGenericConstraint.ts(9,24): error TS4105: Private or protected member 'a' cannot be accessed on a type parameter.
tests/cases/compiler/indexedAccessPrivateMemberOfGenericConstraint.ts(9,32): error TS4105: Private or protected member 'a' cannot be accessed on a type parameter.
tests/cases/compiler/indexedAccessPrivateMemberOfGenericConstraint.ts(10,27): error TS4105: Private or protected member 'a' cannot be accessed on a type parameter.
tests/cases/compiler/indexedAccessPrivateMemberOfGenericConstraint.ts(11,27): error TS4105: Private or protected member 'a' cannot be accessed on a type parameter.


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

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

Expand Down Expand Up @@ -44,6 +45,7 @@ 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 intersection 'A & B' was reduced to 'never' because property 'kind' has conflicting types in some constituents.

declare let x: A | (B & C); // A
let a: A = x;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
tests/cases/conformance/types/intersection/intersectionReductionStrict.ts(38,4): error TS2339: Property 'kind' does not exist on type 'never'.
The intersection 'A & B' was reduced to 'never' because property 'kind' has conflicting types in some constituents.
tests/cases/conformance/types/intersection/intersectionReductionStrict.ts(69,1): error TS2322: Type 'any' is not assignable to type 'never'.
tests/cases/conformance/types/intersection/intersectionReductionStrict.ts(70,1): error TS2322: Type 'any' is not assignable to type 'never'.

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

declare let x: A | (B & C); // A
let a: A = x;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
tests/cases/compiler/intersectionWithConflictingPrivates.ts(5,4): error TS2339: Property 'y' does not exist on type 'never'.
The intersection 'A & B' was reduced to 'never' because property 'x' exists in multiple constituents and is private in some.
tests/cases/compiler/intersectionWithConflictingPrivates.ts(6,1): error TS2322: Type '{}' is not assignable to type 'never'.
The intersection 'A & B' was reduced to 'never' because property 'x' exists in multiple constituents and is private in some.


==== tests/cases/compiler/intersectionWithConflictingPrivates.ts (2 errors) ====
class A { private x: unknown; y?: string; }
class B { private x: unknown; y?: string; }

declare let ab: A & B;
ab.y = 'hello';
~
!!! error TS2339: Property 'y' does not exist on type 'never'.
!!! error TS2339: The intersection 'A & B' was reduced to 'never' because property 'x' exists in multiple constituents and is private in some.
ab = {};
~~
!!! error TS2322: Type '{}' is not assignable to type 'never'.
!!! error TS2322: The intersection 'A & B' was reduced to 'never' because property 'x' exists in multiple constituents and is private in some.

function f1(node: A | B) {
if (node instanceof A || node instanceof A) {
node; // A
}
else {
node; // B
}
node; // A | B
}

// Repro from #37659

abstract class ViewNode { }
abstract class ViewRefNode extends ViewNode { }
abstract class ViewRefFileNode extends ViewRefNode { }

class CommitFileNode extends ViewRefFileNode {
private _id: any;
}

class ResultsFileNode extends ViewRefFileNode {
private _id: any;
}

class StashFileNode extends CommitFileNode {
private _id2: any;
}

class StatusFileNode extends ViewNode {
private _id: any;
}

class Foo {
private async foo(node: CommitFileNode | ResultsFileNode | StashFileNode) {
if (
!(node instanceof CommitFileNode) &&
!(node instanceof StashFileNode) &&
!(node instanceof ResultsFileNode)
) {
return;
}

await this.bar(node);
}

private async bar(node: CommitFileNode | ResultsFileNode | StashFileNode | StatusFileNode, options?: {}) {
return Promise.resolve(undefined);
}
}

Loading