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

Add unmeasurable variance kind for marking types whose variance result is unreliable #30416

Merged
merged 13 commits into from May 3, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
28 changes: 27 additions & 1 deletion src/compiler/checker.ts
Expand Up @@ -670,6 +670,7 @@ namespace ts {

let _jsxNamespace: __String;
let _jsxFactoryEntity: EntityName | undefined;
let unmeasurableMarkerHandler: (() => void) | undefined;

const subtypeRelation = createMap<RelationComparisonResult>();
const assignableRelation = createMap<RelationComparisonResult>();
Expand Down Expand Up @@ -12973,6 +12974,9 @@ namespace ts {
}

function relateVariances(sourceTypeArguments: ReadonlyArray<Type> | undefined, targetTypeArguments: ReadonlyArray<Type> | undefined, variances: Variance[]) {
if (some(variances, v => v === Variance.Unmeasurable)) {
return undefined;
}
if (result = typeArgumentsRelatedTo(sourceTypeArguments, targetTypeArguments, variances, reportErrors)) {
return result;
}
Expand Down Expand Up @@ -13006,6 +13010,13 @@ namespace ts {
}
}

function reportUnmeasurableMarkers(p: TypeParameter) {
if (unmeasurableMarkerHandler && (p === markerSuperType || p === markerSubType || p === markerOtherType)) {
unmeasurableMarkerHandler();
}
return p;
}

// A type [P in S]: X is related to a type [Q in T]: Y if T is related to S and X' is
// related to Y, where X' is an instantiation of X in which P is replaced with Q. Notice
// that S and T are contra-variant whereas X and Y are co-variant.
Expand All @@ -13014,7 +13025,9 @@ namespace ts {
getCombinedMappedTypeOptionality(source) <= getCombinedMappedTypeOptionality(target));
if (modifiersRelated) {
let result: Ternary;
if (result = isRelatedTo(getConstraintTypeFromMappedType(target), getConstraintTypeFromMappedType(source), reportErrors)) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is it possible to only report as unmeasurable when the mapped types have modifiers?

Copy link
Member Author

Choose a reason for hiding this comment

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

Isn't the Record example exactly the case of when there isn't a modifier?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yep, adding the modifier check means that this will not fix #29698. But the two issues aren't exactly the same so I was wondering if there exists two distinct fixes. #29698 only really breaks because when using string as a key type.

Copy link
Collaborator

Choose a reason for hiding this comment

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

This was the kind of (hack) I had in mind: https://pastebin.com/n431Puhu. I have no idea if this is even feasible, or correct. I thought I'd at least play around with something rather than solely firing off random questions.

const targetConstraint = getConstraintTypeFromMappedType(target);
const sourceConstraint = instantiateType(getConstraintTypeFromMappedType(source), reportUnmeasurableMarkers);
if (result = isRelatedTo(targetConstraint, sourceConstraint, reportErrors)) {
const mapper = createTypeMapper([getTypeParameterFromMappedType(source)], [getTypeParameterFromMappedType(target)]);
return result & isRelatedTo(instantiateType(getTemplateTypeFromMappedType(source), mapper), getTemplateTypeFromMappedType(target), reportErrors);
}
Expand Down Expand Up @@ -13489,6 +13502,9 @@ namespace ts {
cache.variances = emptyArray;
variances = [];
for (const tp of typeParameters) {
let unmeasurable = false;
const oldHandler = unmeasurableMarkerHandler;
unmeasurableMarkerHandler = () => unmeasurable = true;
// We first compare instantiations where the type parameter is replaced with
// marker types that have a known subtype relationship. From this we can infer
// invariance, covariance, contravariance or bivariance.
Expand All @@ -13503,6 +13519,16 @@ namespace ts {
if (variance === Variance.Bivariant && isTypeAssignableTo(createMarkerType(cache, tp, markerOtherType), typeWithSuper)) {
variance = Variance.Independent;
}
unmeasurableMarkerHandler = oldHandler;
if (unmeasurable) {
variance = Variance.Unmeasurable;
const covariantID = getRelationKey(typeWithSub, typeWithSuper, assignableRelation);
const contravariantID = getRelationKey(typeWithSuper, typeWithSub, assignableRelation);
// We delete the results of these checks, as we want them to actually be run, see the `Unmeasurable` variance we cache,
// And then fall back to a structural result.
assignableRelation.delete(covariantID);
assignableRelation.delete(contravariantID);
}
variances.push(variance);
}
cache.variances = variances;
Expand Down
1 change: 1 addition & 0 deletions src/compiler/types.ts
Expand Up @@ -4133,6 +4133,7 @@ namespace ts {
Contravariant = 2, // Contravariant
Bivariant = 3, // Both covariant and contravariant
Independent = 4, // Unwitnessed type parameter
Unmeasurable = 5, // Variance result is unusable - relationship relies on structural comparisons which are not reflected in generic relationships
}

// Generic class and interface types
Expand Down
2 changes: 0 additions & 2 deletions tests/baselines/reference/mappedTypes5.errors.txt
@@ -1,7 +1,6 @@
tests/cases/conformance/types/mapped/mappedTypes5.ts(6,9): error TS2322: Type 'Partial<T>' is not assignable to type 'Readonly<T>'.
tests/cases/conformance/types/mapped/mappedTypes5.ts(8,9): error TS2322: Type 'Partial<Readonly<T>>' is not assignable to type 'Readonly<T>'.
tests/cases/conformance/types/mapped/mappedTypes5.ts(9,9): error TS2322: Type 'Readonly<Partial<T>>' is not assignable to type 'Readonly<T>'.
Type 'Partial<T>' is not assignable to type 'T'.


==== tests/cases/conformance/types/mapped/mappedTypes5.ts (3 errors) ====
Expand All @@ -20,7 +19,6 @@ tests/cases/conformance/types/mapped/mappedTypes5.ts(9,9): error TS2322: Type 'R
let b4: Readonly<T> = rp; // Error
~~
!!! error TS2322: Type 'Readonly<Partial<T>>' is not assignable to type 'Readonly<T>'.
!!! error TS2322: Type 'Partial<T>' is not assignable to type 'T'.
let c1: Partial<Readonly<T>> = p;
let c2: Partial<Readonly<T>> = r;
let c3: Partial<Readonly<T>> = pr;
Expand Down
@@ -0,0 +1,61 @@
tests/cases/compiler/requiredMappedTypeModifierTrumpsVariance.ts(5,1): error TS2741: Property 'a' is missing in type 'Required<{ b?: 1; x: 1; }>' but required in type 'Required<{ a?: 1; x: 1; }>'.
tests/cases/compiler/requiredMappedTypeModifierTrumpsVariance.ts(6,1): error TS2741: Property 'b' is missing in type 'Required<{ a?: 1; x: 1; }>' but required in type 'Required<{ b?: 1; x: 1; }>'.
tests/cases/compiler/requiredMappedTypeModifierTrumpsVariance.ts(8,3): error TS2339: Property 'b' does not exist on type 'Required<{ a?: 1; x: 1; }>'.
tests/cases/compiler/requiredMappedTypeModifierTrumpsVariance.ts(9,3): error TS2339: Property 'a' does not exist on type 'Required<{ b?: 1; x: 1; }>'.
tests/cases/compiler/requiredMappedTypeModifierTrumpsVariance.ts(18,1): error TS2322: Type 'Foo<{ b?: 1; x: 1; }>' is not assignable to type 'Foo<{ a?: 1; x: 1; }>'.
Types of property 'a' are incompatible.
Property 'a' is missing in type 'Required<{ b?: 1; x: 1; }>' but required in type 'Required<{ a?: 1; x: 1; }>'.
tests/cases/compiler/requiredMappedTypeModifierTrumpsVariance.ts(19,1): error TS2322: Type 'Foo<{ a?: 1; x: 1; }>' is not assignable to type 'Foo<{ b?: 1; x: 1; }>'.
Types of property 'a' are incompatible.
Property 'b' is missing in type 'Required<{ a?: 1; x: 1; }>' but required in type 'Required<{ b?: 1; x: 1; }>'.
tests/cases/compiler/requiredMappedTypeModifierTrumpsVariance.ts(21,6): error TS2339: Property 'b' does not exist on type 'Required<{ a?: 1; x: 1; }>'.
tests/cases/compiler/requiredMappedTypeModifierTrumpsVariance.ts(22,6): error TS2339: Property 'a' does not exist on type 'Required<{ b?: 1; x: 1; }>'.


==== tests/cases/compiler/requiredMappedTypeModifierTrumpsVariance.ts (8 errors) ====
const a: Required<{ a?: 1; x: 1 }> = { a: 1, x: 1 };
const b: Required<{ b?: 1; x: 1 }> = { b: 1, x: 1 };
export let A = a;
export let B = b;
A = b; // Should Error
~
!!! error TS2741: Property 'a' is missing in type 'Required<{ b?: 1; x: 1; }>' but required in type 'Required<{ a?: 1; x: 1; }>'.
!!! related TS2728 tests/cases/compiler/requiredMappedTypeModifierTrumpsVariance.ts:1:21: 'a' is declared here.
B = a; // Should Error
~
!!! error TS2741: Property 'b' is missing in type 'Required<{ a?: 1; x: 1; }>' but required in type 'Required<{ b?: 1; x: 1; }>'.
!!! related TS2728 tests/cases/compiler/requiredMappedTypeModifierTrumpsVariance.ts:2:21: 'b' is declared here.

a.b; // Property 'b' does not exist on type 'Required<{ a?: 1; x: 1; }>'.
~
!!! error TS2339: Property 'b' does not exist on type 'Required<{ a?: 1; x: 1; }>'.
b.a; // Property 'a' does not exist on type 'Required<{ b?: 1; x: 1; }>'.
~
!!! error TS2339: Property 'a' does not exist on type 'Required<{ b?: 1; x: 1; }>'.

interface Foo<T> {
a: Required<T>;
}
const aa: Foo<{ a?: 1; x: 1 }> = { a: { a: 1, x: 1 } };
const bb: Foo<{ b?: 1; x: 1 }> = { a: { b: 1, x: 1 } };
export let AA = aa;
export let BB = bb;
AA = bb; // Should Error
~~
!!! error TS2322: Type 'Foo<{ b?: 1; x: 1; }>' is not assignable to type 'Foo<{ a?: 1; x: 1; }>'.
!!! error TS2322: Types of property 'a' are incompatible.
!!! error TS2322: Property 'a' is missing in type 'Required<{ b?: 1; x: 1; }>' but required in type 'Required<{ a?: 1; x: 1; }>'.
!!! related TS2728 tests/cases/compiler/requiredMappedTypeModifierTrumpsVariance.ts:14:17: 'a' is declared here.
BB = aa; // Should Error
~~
!!! error TS2322: Type 'Foo<{ a?: 1; x: 1; }>' is not assignable to type 'Foo<{ b?: 1; x: 1; }>'.
!!! error TS2322: Types of property 'a' are incompatible.
!!! error TS2322: Property 'b' is missing in type 'Required<{ a?: 1; x: 1; }>' but required in type 'Required<{ b?: 1; x: 1; }>'.
!!! related TS2728 tests/cases/compiler/requiredMappedTypeModifierTrumpsVariance.ts:15:17: 'b' is declared here.

aa.a.b; // Property 'b' does not exist on type 'Required<{ a?: 1; x: 1; }>'.
~
!!! error TS2339: Property 'b' does not exist on type 'Required<{ a?: 1; x: 1; }>'.
bb.a.a; // Property 'a' does not exist on type 'Required<{ b?: 1; x: 1; }>'.
~
!!! error TS2339: Property 'a' does not exist on type 'Required<{ b?: 1; x: 1; }>'.
@@ -0,0 +1,43 @@
//// [requiredMappedTypeModifierTrumpsVariance.ts]
const a: Required<{ a?: 1; x: 1 }> = { a: 1, x: 1 };
const b: Required<{ b?: 1; x: 1 }> = { b: 1, x: 1 };
export let A = a;
export let B = b;
A = b; // Should Error
B = a; // Should Error

a.b; // Property 'b' does not exist on type 'Required<{ a?: 1; x: 1; }>'.
b.a; // Property 'a' does not exist on type 'Required<{ b?: 1; x: 1; }>'.

interface Foo<T> {
a: Required<T>;
}
const aa: Foo<{ a?: 1; x: 1 }> = { a: { a: 1, x: 1 } };
const bb: Foo<{ b?: 1; x: 1 }> = { a: { b: 1, x: 1 } };
export let AA = aa;
export let BB = bb;
AA = bb; // Should Error
BB = aa; // Should Error

aa.a.b; // Property 'b' does not exist on type 'Required<{ a?: 1; x: 1; }>'.
bb.a.a; // Property 'a' does not exist on type 'Required<{ b?: 1; x: 1; }>'.

//// [requiredMappedTypeModifierTrumpsVariance.js]
"use strict";
exports.__esModule = true;
var a = { a: 1, x: 1 };
var b = { b: 1, x: 1 };
exports.A = a;
exports.B = b;
exports.A = b; // Should Error
exports.B = a; // Should Error
a.b; // Property 'b' does not exist on type 'Required<{ a?: 1; x: 1; }>'.
b.a; // Property 'a' does not exist on type 'Required<{ b?: 1; x: 1; }>'.
var aa = { a: { a: 1, x: 1 } };
var bb = { a: { b: 1, x: 1 } };
exports.AA = aa;
exports.BB = bb;
exports.AA = bb; // Should Error
exports.BB = aa; // Should Error
aa.a.b; // Property 'b' does not exist on type 'Required<{ a?: 1; x: 1; }>'.
bb.a.a; // Property 'a' does not exist on type 'Required<{ b?: 1; x: 1; }>'.
@@ -0,0 +1,92 @@
=== tests/cases/compiler/requiredMappedTypeModifierTrumpsVariance.ts ===
const a: Required<{ a?: 1; x: 1 }> = { a: 1, x: 1 };
>a : Symbol(a, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 0, 5))
>Required : Symbol(Required, Decl(lib.es5.d.ts, --, --))
>a : Symbol(a, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 0, 19))
>x : Symbol(x, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 0, 26))
>a : Symbol(a, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 0, 38))
>x : Symbol(x, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 0, 44))

const b: Required<{ b?: 1; x: 1 }> = { b: 1, x: 1 };
>b : Symbol(b, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 1, 5))
>Required : Symbol(Required, Decl(lib.es5.d.ts, --, --))
>b : Symbol(b, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 1, 19))
>x : Symbol(x, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 1, 26))
>b : Symbol(b, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 1, 38))
>x : Symbol(x, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 1, 44))

export let A = a;
>A : Symbol(A, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 2, 10))
>a : Symbol(a, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 0, 5))

export let B = b;
>B : Symbol(B, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 3, 10))
>b : Symbol(b, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 1, 5))

A = b; // Should Error
>A : Symbol(A, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 2, 10))
>b : Symbol(b, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 1, 5))

B = a; // Should Error
>B : Symbol(B, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 3, 10))
>a : Symbol(a, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 0, 5))

a.b; // Property 'b' does not exist on type 'Required<{ a?: 1; x: 1; }>'.
>a : Symbol(a, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 0, 5))

b.a; // Property 'a' does not exist on type 'Required<{ b?: 1; x: 1; }>'.
>b : Symbol(b, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 1, 5))

interface Foo<T> {
>Foo : Symbol(Foo, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 8, 4))
>T : Symbol(T, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 10, 14))

a: Required<T>;
>a : Symbol(Foo.a, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 10, 18))
>Required : Symbol(Required, Decl(lib.es5.d.ts, --, --))
>T : Symbol(T, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 10, 14))
}
const aa: Foo<{ a?: 1; x: 1 }> = { a: { a: 1, x: 1 } };
>aa : Symbol(aa, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 13, 5))
>Foo : Symbol(Foo, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 8, 4))
>a : Symbol(a, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 13, 15))
>x : Symbol(x, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 13, 22))
>a : Symbol(a, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 13, 34))
>a : Symbol(a, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 13, 39))
>x : Symbol(x, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 13, 45))

const bb: Foo<{ b?: 1; x: 1 }> = { a: { b: 1, x: 1 } };
>bb : Symbol(bb, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 14, 5))
>Foo : Symbol(Foo, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 8, 4))
>b : Symbol(b, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 14, 15))
>x : Symbol(x, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 14, 22))
>a : Symbol(a, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 14, 34))
>b : Symbol(b, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 14, 39))
>x : Symbol(x, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 14, 45))

export let AA = aa;
>AA : Symbol(AA, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 15, 10))
>aa : Symbol(aa, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 13, 5))

export let BB = bb;
>BB : Symbol(BB, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 16, 10))
>bb : Symbol(bb, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 14, 5))

AA = bb; // Should Error
>AA : Symbol(AA, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 15, 10))
>bb : Symbol(bb, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 14, 5))

BB = aa; // Should Error
>BB : Symbol(BB, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 16, 10))
>aa : Symbol(aa, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 13, 5))

aa.a.b; // Property 'b' does not exist on type 'Required<{ a?: 1; x: 1; }>'.
>aa.a : Symbol(Foo.a, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 10, 18))
>aa : Symbol(aa, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 13, 5))
>a : Symbol(Foo.a, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 10, 18))

bb.a.a; // Property 'a' does not exist on type 'Required<{ b?: 1; x: 1; }>'.
>bb.a : Symbol(Foo.a, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 10, 18))
>bb : Symbol(bb, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 14, 5))
>a : Symbol(Foo.a, Decl(requiredMappedTypeModifierTrumpsVariance.ts, 10, 18))