Skip to content

Relate non-augmenting subtypes without resorting to structural comparison #43624

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

Merged
33 changes: 30 additions & 3 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17195,12 +17195,13 @@ namespace ts {

function getNormalizedType(type: Type, writing: boolean): Type {
while (true) {
const t = isFreshLiteralType(type) ? (<FreshableType>type).regularType :
let t = isFreshLiteralType(type) ? (<FreshableType>type).regularType :
getObjectFlags(type) & ObjectFlags.Reference && (<TypeReference>type).node ? createTypeReference((<TypeReference>type).target, getTypeArguments(<TypeReference>type)) :
type.flags & TypeFlags.UnionOrIntersection ? getReducedType(type) :
type.flags & TypeFlags.Substitution ? writing ? (<SubstitutionType>type).baseType : (<SubstitutionType>type).substitute :
type.flags & TypeFlags.Simplifiable ? getSimplifiedType(type, writing) :
type;
t = getSingleBaseForNonAugmentingSubtype(t) || t;
if (t === type) break;
type = t;
}
Expand Down Expand Up @@ -17702,8 +17703,10 @@ namespace ts {

function reportErrorResults(source: Type, target: Type, result: Ternary, isComparingJsxAttributes: boolean) {
if (!result && reportErrors) {
source = originalSource.aliasSymbol ? originalSource : source;
target = originalTarget.aliasSymbol ? originalTarget : target;
const sourceHasBase = !!getSingleBaseForNonAugmentingSubtype(originalSource);
const targetHasBase = !!getSingleBaseForNonAugmentingSubtype(originalTarget);
source = (originalSource.aliasSymbol || sourceHasBase) ? originalSource : source;
target = (originalTarget.aliasSymbol || targetHasBase) ? originalTarget : target;
let maybeSuppress = overrideNextErrorInfo > 0;
if (maybeSuppress) {
overrideNextErrorInfo--;
Expand Down Expand Up @@ -19956,6 +19959,30 @@ namespace ts {
return isArrayType(type) || !(type.flags & TypeFlags.Nullable) && isTypeAssignableTo(type, anyReadonlyArrayType);
}

function getSingleBaseForNonAugmentingSubtype(type: Type) {
if (!(getObjectFlags(type) & ObjectFlags.Reference) || !(getObjectFlags((type as TypeReference).target) & ObjectFlags.ClassOrInterface)) {
return undefined;
}
if (getObjectFlags(type) & ObjectFlags.IdenticalBaseTypeCalculated) {
return getObjectFlags(type) & ObjectFlags.IdenticalBaseTypeExists ? (type as TypeReference).cachedEquivalentBaseType : undefined;
}
(type as TypeReference).objectFlags |= ObjectFlags.IdenticalBaseTypeCalculated;
const target = (type as TypeReference).target as InterfaceType;
const bases = getBaseTypes(target);
if (bases.length !== 1) {
return undefined;
}
if (getMembersOfSymbol(type.symbol).size) {
return undefined; // If the interface has any members, they may subtype members in the base, so we should do a full structural comparison
}
let instantiatedBase = !length(target.typeParameters) ? bases[0] : instantiateType(bases[0], createTypeMapper(target.typeParameters!, getTypeArguments(type as TypeReference).slice(0, target.typeParameters!.length)));
if (length(getTypeArguments(type as TypeReference)) > length(target.typeParameters)) {
instantiatedBase = getTypeWithThisArgument(instantiatedBase, last(getTypeArguments(type as TypeReference)));
}
(type as TypeReference).objectFlags |= ObjectFlags.IdenticalBaseTypeExists;
return (type as TypeReference).cachedEquivalentBaseType = instantiatedBase;
}

function isEmptyArrayLiteralType(type: Type): boolean {
const elementType = getElementTypeOfArrayType(type);
return strictNullChecks ? elementType === implicitNeverType : elementType === undefinedWideningType;
Expand Down
7 changes: 7 additions & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5200,6 +5200,11 @@ namespace ts {
ObjectRestType = 1 << 23, // Originates in object rest declaration
/* @internal */
IsClassInstanceClone = 1 << 24, // Type is a clone of a class instance type
// Flags that require TypeFlags.Object and ObjectFlags.Reference
/* @internal */
IdenticalBaseTypeCalculated = 1 << 25, // has had `getSingleBaseForNonAugmentingSubtype` invoked on it already
/* @internal */
IdenticalBaseTypeExists = 1 << 26, // has a defined cachedEquivalentBaseType member

// Flags that require TypeFlags.UnionOrIntersection
/* @internal */
Expand Down Expand Up @@ -5281,6 +5286,8 @@ namespace ts {
resolvedTypeArguments?: readonly Type[]; // Resolved type reference type arguments
/* @internal */
literalType?: TypeReference; // Clone of type with ObjectFlags.ArrayLiteral set
/* @internal */
cachedEquivalentBaseType?: Type; // Only set on references to class or interfaces with a single base type and no augmentations
}

export interface DeferredTypeReference extends TypeReference {
Expand Down
10 changes: 4 additions & 6 deletions tests/baselines/reference/arrayLiterals3.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ tests/cases/conformance/expressions/arrayLiterals/arrayLiterals3.ts(17,5): error
tests/cases/conformance/expressions/arrayLiterals/arrayLiterals3.ts(33,5): error TS2322: Type 'number[]' is not assignable to type '[number, number, number]'.
Target requires 3 element(s) but source may have fewer.
tests/cases/conformance/expressions/arrayLiterals/arrayLiterals3.ts(34,5): error TS2322: Type '(string | number)[]' is not assignable to type 'myArray'.
The types returned by 'pop()' are incompatible between these types.
Type 'string | number' is not assignable to type 'Number'.
Type 'string' is not assignable to type 'Number'.
Type 'string | number' is not assignable to type 'Number'.
Type 'string' is not assignable to type 'Number'.


==== tests/cases/conformance/expressions/arrayLiterals/arrayLiterals3.ts (7 errors) ====
Expand Down Expand Up @@ -65,7 +64,6 @@ tests/cases/conformance/expressions/arrayLiterals/arrayLiterals3.ts(34,5): error
var c2: myArray = [...temp1, ...temp]; // Error cannot assign (number|string)[] to number[]
~~
!!! error TS2322: Type '(string | number)[]' is not assignable to type 'myArray'.
!!! error TS2322: The types returned by 'pop()' are incompatible between these types.
!!! error TS2322: Type 'string | number' is not assignable to type 'Number'.
!!! error TS2322: Type 'string' is not assignable to type 'Number'.
!!! error TS2322: Type 'string | number' is not assignable to type 'Number'.
!!! error TS2322: Type 'string' is not assignable to type 'Number'.

Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignme
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithObjectMembersAccessibility.ts(81,5): error TS2322: Type 'Base' is not assignable to type '{ foo: string; }'.
Property 'foo' is private in type 'Base' but not in type '{ foo: string; }'.
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithObjectMembersAccessibility.ts(82,5): error TS2322: Type 'I' is not assignable to type '{ foo: string; }'.
Property 'foo' is private in type 'I' but not in type '{ foo: string; }'.
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithObjectMembersAccessibility.ts(84,5): error TS2322: Type 'E' is not assignable to type '{ foo: string; }'.
Property 'foo' is private in type 'E' but not in type '{ foo: string; }'.
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithObjectMembersAccessibility.ts(86,5): error TS2322: Type '{ foo: string; }' is not assignable to type 'Base'.
Expand All @@ -27,23 +26,18 @@ tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignme
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithObjectMembersAccessibility.ts(89,5): error TS2322: Type 'E' is not assignable to type 'Base'.
Types have separate declarations of a private property 'foo'.
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithObjectMembersAccessibility.ts(92,5): error TS2322: Type '{ foo: string; }' is not assignable to type 'I'.
Property 'foo' is private in type 'I' but not in type '{ foo: string; }'.
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithObjectMembersAccessibility.ts(94,5): error TS2322: Type 'D' is not assignable to type 'I'.
Property 'foo' is private in type 'I' but not in type 'D'.
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithObjectMembersAccessibility.ts(95,5): error TS2322: Type 'E' is not assignable to type 'I'.
Types have separate declarations of a private property 'foo'.
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithObjectMembersAccessibility.ts(99,5): error TS2322: Type 'Base' is not assignable to type 'D'.
Property 'foo' is private in type 'Base' but not in type 'D'.
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithObjectMembersAccessibility.ts(100,5): error TS2322: Type 'I' is not assignable to type 'D'.
Property 'foo' is private in type 'I' but not in type 'D'.
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithObjectMembersAccessibility.ts(101,5): error TS2322: Type 'E' is not assignable to type 'D'.
Property 'foo' is private in type 'E' but not in type 'D'.
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithObjectMembersAccessibility.ts(103,5): error TS2322: Type '{ foo: string; }' is not assignable to type 'E'.
Property 'foo' is private in type 'E' but not in type '{ foo: string; }'.
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithObjectMembersAccessibility.ts(104,5): error TS2322: Type 'Base' is not assignable to type 'E'.
Types have separate declarations of a private property 'foo'.
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithObjectMembersAccessibility.ts(105,5): error TS2322: Type 'I' is not assignable to type 'E'.
Types have separate declarations of a private property 'foo'.
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithObjectMembersAccessibility.ts(106,5): error TS2322: Type 'D' is not assignable to type 'E'.
Property 'foo' is private in type 'E' but not in type 'D'.

Expand Down Expand Up @@ -160,7 +154,6 @@ tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignme
a = i; // error
~
!!! error TS2322: Type 'I' is not assignable to type '{ foo: string; }'.
!!! error TS2322: Property 'foo' is private in type 'I' but not in type '{ foo: string; }'.
a = d;
a = e; // error
~
Expand All @@ -185,16 +178,13 @@ tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignme
i = a; // error
~
!!! error TS2322: Type '{ foo: string; }' is not assignable to type 'I'.
!!! error TS2322: Property 'foo' is private in type 'I' but not in type '{ foo: string; }'.
i = b;
i = d; // error
~
!!! error TS2322: Type 'D' is not assignable to type 'I'.
!!! error TS2322: Property 'foo' is private in type 'I' but not in type 'D'.
i = e; // error
~
!!! error TS2322: Type 'E' is not assignable to type 'I'.
!!! error TS2322: Types have separate declarations of a private property 'foo'.
i = i;

d = a;
Expand All @@ -205,7 +195,6 @@ tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignme
d = i; // error
~
!!! error TS2322: Type 'I' is not assignable to type 'D'.
!!! error TS2322: Property 'foo' is private in type 'I' but not in type 'D'.
d = e; // error
~
!!! error TS2322: Type 'E' is not assignable to type 'D'.
Expand All @@ -222,7 +211,6 @@ tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignme
e = i; // errror
~
!!! error TS2322: Type 'I' is not assignable to type 'E'.
!!! error TS2322: Types have separate declarations of a private property 'foo'.
e = d; // errror
~
!!! error TS2322: Type 'D' is not assignable to type 'E'.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
tests/cases/conformance/jsx/file.tsx(20,10): error TS2741: Property 'children' is missing in type '{ a: number; b: string; }' but required in type 'Prop'.
tests/cases/conformance/jsx/file.tsx(25,9): error TS2740: Type 'Element' is missing the following properties from type 'Button': render, setState, forceUpdate, state, and 2 more.
tests/cases/conformance/jsx/file.tsx(25,9): error TS2740: Type 'ReactElement<any>' is missing the following properties from type 'Button': render, setState, forceUpdate, state, and 2 more.
tests/cases/conformance/jsx/file.tsx(29,10): error TS2740: Type 'typeof Button' is missing the following properties from type 'Button': render, setState, forceUpdate, props, and 3 more.


Expand Down Expand Up @@ -33,7 +33,7 @@ tests/cases/conformance/jsx/file.tsx(29,10): error TS2740: Type 'typeof Button'
<Comp a={10} b="hi">
<Button />
~~~~~~~~~~
!!! error TS2740: Type 'Element' is missing the following properties from type 'Button': render, setState, forceUpdate, state, and 2 more.
!!! error TS2740: Type 'ReactElement<any>' is missing the following properties from type 'Button': render, setState, forceUpdate, state, and 2 more.
!!! related TS6500 tests/cases/conformance/jsx/file.tsx:6:5: The expected type comes from property 'children' which is declared here on type 'IntrinsicAttributes & Prop'
</Comp>;
let k2 =
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/classImplementsClass4.errors.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
tests/cases/compiler/classImplementsClass4.ts(5,7): error TS2720: Class 'C' incorrectly implements class 'A'. Did you mean to extend 'A' and inherit its members as a subclass?
Property 'x' is missing in type 'C' but required in type 'A'.
tests/cases/compiler/classImplementsClass4.ts(16,1): error TS2741: Property 'x' is missing in type 'C' but required in type 'C2'.
tests/cases/compiler/classImplementsClass4.ts(16,1): error TS2741: Property 'x' is missing in type 'C' but required in type 'A'.


==== tests/cases/compiler/classImplementsClass4.ts (2 errors) ====
Expand All @@ -25,5 +25,5 @@ tests/cases/compiler/classImplementsClass4.ts(16,1): error TS2741: Property 'x'
c = c2;
c2 = c;
~~
!!! error TS2741: Property 'x' is missing in type 'C' but required in type 'C2'.
!!! error TS2741: Property 'x' is missing in type 'C' but required in type 'A'.
!!! related TS2728 tests/cases/compiler/classImplementsClass4.ts:2:13: 'x' is declared here.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
tests/cases/compiler/crashInsourcePropertyIsRelatableToTargetProperty.ts(9,5): error TS2741: Property 'x' is missing in type '(x: "hi", items: string[]) => typeof foo' but required in type 'D'.
tests/cases/compiler/crashInsourcePropertyIsRelatableToTargetProperty.ts(9,5): error TS2741: Property 'x' is missing in type '(x: "hi", items: string[]) => typeof foo' but required in type 'C'.


==== tests/cases/compiler/crashInsourcePropertyIsRelatableToTargetProperty.ts (1 errors) ====
Expand All @@ -12,6 +12,6 @@ tests/cases/compiler/crashInsourcePropertyIsRelatableToTargetProperty.ts(9,5): e
}
var a: D = foo("hi", []);
~
!!! error TS2741: Property 'x' is missing in type '(x: "hi", items: string[]) => typeof foo' but required in type 'D'.
!!! error TS2741: Property 'x' is missing in type '(x: "hi", items: string[]) => typeof foo' but required in type 'C'.
!!! related TS2728 tests/cases/compiler/crashInsourcePropertyIsRelatableToTargetProperty.ts:2:13: 'x' is declared here.

Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ tests/cases/conformance/interfaces/interfacesExtendingClasses/implementingAnInte
tests/cases/conformance/interfaces/interfacesExtendingClasses/implementingAnInterfaceExtendingClassWithProtecteds.ts(21,7): error TS2420: Class 'Bar4' incorrectly implements interface 'I'.
Property 'x' is protected but type 'Bar4' is not a class derived from 'Foo'.
tests/cases/conformance/interfaces/interfacesExtendingClasses/implementingAnInterfaceExtendingClassWithProtecteds.ts(26,7): error TS2420: Class 'Bar5' incorrectly implements interface 'I'.
Property 'y' is missing in type 'Bar5' but required in type 'I'.
Property 'y' is missing in type 'Foo' but required in type 'I'.
tests/cases/conformance/interfaces/interfacesExtendingClasses/implementingAnInterfaceExtendingClassWithProtecteds.ts(29,7): error TS2420: Class 'Bar6' incorrectly implements interface 'I'.
Property 'y' is protected in type 'Bar6' but public in type 'I'.

Expand Down Expand Up @@ -54,7 +54,7 @@ tests/cases/conformance/interfaces/interfacesExtendingClasses/implementingAnInte
class Bar5 extends Foo implements I { // error
~~~~
!!! error TS2420: Class 'Bar5' incorrectly implements interface 'I'.
!!! error TS2420: Property 'y' is missing in type 'Bar5' but required in type 'I'.
!!! error TS2420: Property 'y' is missing in type 'Foo' but required in type 'I'.
!!! related TS2728 tests/cases/conformance/interfaces/interfacesExtendingClasses/implementingAnInterfaceExtendingClassWithProtecteds.ts:6:5: 'y' is declared here.
}

Expand Down
Loading