From 529ba99e298c8e4e8f83c6751748a9d99c913e28 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 21 Jun 2022 06:42:10 -0700 Subject: [PATCH] Use separate marker types for variance annotation validation (#49616) * Use separate marker types for variance annotation validation * Add regression test --- src/compiler/checker.ts | 14 ++-- .../varianceAnnotationValidation.errors.txt | 48 ++++++++++++++ .../reference/varianceAnnotationValidation.js | 64 ++++++++++++++++++ .../varianceAnnotationValidation.symbols | 66 +++++++++++++++++++ .../varianceAnnotationValidation.types | 51 ++++++++++++++ .../compiler/varianceAnnotationValidation.ts | 30 +++++++++ 6 files changed, 268 insertions(+), 5 deletions(-) create mode 100644 tests/baselines/reference/varianceAnnotationValidation.errors.txt create mode 100644 tests/baselines/reference/varianceAnnotationValidation.js create mode 100644 tests/baselines/reference/varianceAnnotationValidation.symbols create mode 100644 tests/baselines/reference/varianceAnnotationValidation.types create mode 100644 tests/cases/compiler/varianceAnnotationValidation.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6b45b23c1e8..65c89db785b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -861,6 +861,10 @@ namespace ts { markerSubType.constraint = markerSuperType; const markerOtherType = createTypeParameter(); + const markerSuperTypeForCheck = createTypeParameter(); + const markerSubTypeForCheck = createTypeParameter(); + markerSubTypeForCheck.constraint = markerSuperTypeForCheck; + const noTypePredicate = createTypePredicate(TypePredicateKind.Identifier, "<>", 0, anyType); const anySignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); @@ -5047,8 +5051,8 @@ namespace ts { if (type.symbol) { return symbolToTypeNode(type.symbol, context, SymbolFlags.Type); } - const name = (type === markerSuperType || type === markerSubType) && varianceTypeParameter && varianceTypeParameter.symbol ? - (type === markerSubType ? "sub-" : "super-") + symbolName(varianceTypeParameter.symbol) : "?"; + const name = (type === markerSuperTypeForCheck || type === markerSubTypeForCheck) && varianceTypeParameter && varianceTypeParameter.symbol ? + (type === markerSubTypeForCheck ? "sub-" : "super-") + symbolName(varianceTypeParameter.symbol) : "?"; return factory.createTypeReferenceNode(factory.createIdentifier(name), /*typeArguments*/ undefined); } if (type.flags & TypeFlags.Union && (type as UnionType).origin) { @@ -18543,7 +18547,7 @@ namespace ts { generalizedSourceType = getTypeNameForErrorDisplay(generalizedSource); } - if (target.flags & TypeFlags.TypeParameter && target !== markerSuperType && target !== markerSubType) { + if (target.flags & TypeFlags.TypeParameter && target !== markerSuperTypeForCheck && target !== markerSubTypeForCheck) { const constraint = getBaseConstraintOfType(target); let needsOriginalSource; if (constraint && (isTypeAssignableTo(generalizedSource, constraint) || (needsOriginalSource = isTypeAssignableTo(source, constraint)))) { @@ -35041,8 +35045,8 @@ namespace ts { error(node, Diagnostics.Variance_annotations_are_only_supported_in_type_aliases_for_object_function_constructor_and_mapped_types); } else if (modifiers === ModifierFlags.In || modifiers === ModifierFlags.Out) { - const source = createMarkerType(symbol, typeParameter, modifiers === ModifierFlags.Out ? markerSubType : markerSuperType); - const target = createMarkerType(symbol, typeParameter, modifiers === ModifierFlags.Out ? markerSuperType : markerSubType); + const source = createMarkerType(symbol, typeParameter, modifiers === ModifierFlags.Out ? markerSubTypeForCheck : markerSuperTypeForCheck); + const target = createMarkerType(symbol, typeParameter, modifiers === ModifierFlags.Out ? markerSuperTypeForCheck : markerSubTypeForCheck); const saveVarianceTypeParameter = typeParameter; varianceTypeParameter = typeParameter; checkTypeAssignableTo(source, target, node, Diagnostics.Type_0_is_not_assignable_to_type_1_as_implied_by_variance_annotation); diff --git a/tests/baselines/reference/varianceAnnotationValidation.errors.txt b/tests/baselines/reference/varianceAnnotationValidation.errors.txt new file mode 100644 index 00000000000..228cd7a0912 --- /dev/null +++ b/tests/baselines/reference/varianceAnnotationValidation.errors.txt @@ -0,0 +1,48 @@ +tests/cases/compiler/varianceAnnotationValidation.ts(5,22): error TS2636: Type 'Controller' is not assignable to type 'Controller' as implied by variance annotation. + Types of property 'run' are incompatible. + Type '(animal: sub-T) => void' is not assignable to type '(animal: super-T) => void'. + Types of parameters 'animal' and 'animal' are incompatible. + Type 'super-T' is not assignable to type 'sub-T'. +tests/cases/compiler/varianceAnnotationValidation.ts(27,1): error TS2322: Type 'AnimalContainer' is not assignable to type 'AnimalContainer'. + Property 'bark' is missing in type 'Animal' but required in type 'Dog'. + + +==== tests/cases/compiler/varianceAnnotationValidation.ts (2 errors) ==== + // Repro from #49607 + + // Variance annotation error expected + + interface Controller { + ~~~~~ +!!! error TS2636: Type 'Controller' is not assignable to type 'Controller' as implied by variance annotation. +!!! error TS2636: Types of property 'run' are incompatible. +!!! error TS2636: Type '(animal: sub-T) => void' is not assignable to type '(animal: super-T) => void'. +!!! error TS2636: Types of parameters 'animal' and 'animal' are incompatible. +!!! error TS2636: Type 'super-T' is not assignable to type 'sub-T'. + createAnimal: () => T; + run: (animal: T) => void; + } + + interface Animal { + run(): void; + }; + + class Dog implements Animal { + run() {}; + bark() {}; + } + + interface AnimalContainer { + controller: Controller; + } + + declare let ca: AnimalContainer; + declare let cd: AnimalContainer; + + ca = cd; // Ok + cd = ca; // Error + ~~ +!!! error TS2322: Type 'AnimalContainer' is not assignable to type 'AnimalContainer'. +!!! error TS2322: Property 'bark' is missing in type 'Animal' but required in type 'Dog'. +!!! related TS2728 tests/cases/compiler/varianceAnnotationValidation.ts:16:2: 'bark' is declared here. + \ No newline at end of file diff --git a/tests/baselines/reference/varianceAnnotationValidation.js b/tests/baselines/reference/varianceAnnotationValidation.js new file mode 100644 index 00000000000..2c877c7cd93 --- /dev/null +++ b/tests/baselines/reference/varianceAnnotationValidation.js @@ -0,0 +1,64 @@ +//// [varianceAnnotationValidation.ts] +// Repro from #49607 + +// Variance annotation error expected + +interface Controller { + createAnimal: () => T; + run: (animal: T) => void; +} + +interface Animal { + run(): void; +}; + +class Dog implements Animal { + run() {}; + bark() {}; +} + +interface AnimalContainer { + controller: Controller; +} + +declare let ca: AnimalContainer; +declare let cd: AnimalContainer; + +ca = cd; // Ok +cd = ca; // Error + + +//// [varianceAnnotationValidation.js] +"use strict"; +// Repro from #49607 +; +var Dog = /** @class */ (function () { + function Dog() { + } + Dog.prototype.run = function () { }; + ; + Dog.prototype.bark = function () { }; + ; + return Dog; +}()); +ca = cd; // Ok +cd = ca; // Error + + +//// [varianceAnnotationValidation.d.ts] +interface Controller { + createAnimal: () => T; + run: (animal: T) => void; +} +interface Animal { + run(): void; +} +declare class Dog implements Animal { + run(): void; + bark(): void; +} +interface AnimalContainer { + controller: Controller; +} +declare let ca: AnimalContainer; +declare let cd: AnimalContainer; diff --git a/tests/baselines/reference/varianceAnnotationValidation.symbols b/tests/baselines/reference/varianceAnnotationValidation.symbols new file mode 100644 index 00000000000..da743edea79 --- /dev/null +++ b/tests/baselines/reference/varianceAnnotationValidation.symbols @@ -0,0 +1,66 @@ +=== tests/cases/compiler/varianceAnnotationValidation.ts === +// Repro from #49607 + +// Variance annotation error expected + +interface Controller { +>Controller : Symbol(Controller, Decl(varianceAnnotationValidation.ts, 0, 0)) +>T : Symbol(T, Decl(varianceAnnotationValidation.ts, 4, 21)) + + createAnimal: () => T; +>createAnimal : Symbol(Controller.createAnimal, Decl(varianceAnnotationValidation.ts, 4, 29)) +>T : Symbol(T, Decl(varianceAnnotationValidation.ts, 4, 21)) + + run: (animal: T) => void; +>run : Symbol(Controller.run, Decl(varianceAnnotationValidation.ts, 5, 23)) +>animal : Symbol(animal, Decl(varianceAnnotationValidation.ts, 6, 7)) +>T : Symbol(T, Decl(varianceAnnotationValidation.ts, 4, 21)) +} + +interface Animal { +>Animal : Symbol(Animal, Decl(varianceAnnotationValidation.ts, 7, 1)) + + run(): void; +>run : Symbol(Animal.run, Decl(varianceAnnotationValidation.ts, 9, 18)) + +}; + +class Dog implements Animal { +>Dog : Symbol(Dog, Decl(varianceAnnotationValidation.ts, 11, 2)) +>Animal : Symbol(Animal, Decl(varianceAnnotationValidation.ts, 7, 1)) + + run() {}; +>run : Symbol(Dog.run, Decl(varianceAnnotationValidation.ts, 13, 29)) + + bark() {}; +>bark : Symbol(Dog.bark, Decl(varianceAnnotationValidation.ts, 14, 10)) +} + +interface AnimalContainer { +>AnimalContainer : Symbol(AnimalContainer, Decl(varianceAnnotationValidation.ts, 16, 1)) +>T : Symbol(T, Decl(varianceAnnotationValidation.ts, 18, 26)) + + controller: Controller; +>controller : Symbol(AnimalContainer.controller, Decl(varianceAnnotationValidation.ts, 18, 30)) +>Controller : Symbol(Controller, Decl(varianceAnnotationValidation.ts, 0, 0)) +>T : Symbol(T, Decl(varianceAnnotationValidation.ts, 18, 26)) +} + +declare let ca: AnimalContainer; +>ca : Symbol(ca, Decl(varianceAnnotationValidation.ts, 22, 11)) +>AnimalContainer : Symbol(AnimalContainer, Decl(varianceAnnotationValidation.ts, 16, 1)) +>Animal : Symbol(Animal, Decl(varianceAnnotationValidation.ts, 7, 1)) + +declare let cd: AnimalContainer; +>cd : Symbol(cd, Decl(varianceAnnotationValidation.ts, 23, 11)) +>AnimalContainer : Symbol(AnimalContainer, Decl(varianceAnnotationValidation.ts, 16, 1)) +>Dog : Symbol(Dog, Decl(varianceAnnotationValidation.ts, 11, 2)) + +ca = cd; // Ok +>ca : Symbol(ca, Decl(varianceAnnotationValidation.ts, 22, 11)) +>cd : Symbol(cd, Decl(varianceAnnotationValidation.ts, 23, 11)) + +cd = ca; // Error +>cd : Symbol(cd, Decl(varianceAnnotationValidation.ts, 23, 11)) +>ca : Symbol(ca, Decl(varianceAnnotationValidation.ts, 22, 11)) + diff --git a/tests/baselines/reference/varianceAnnotationValidation.types b/tests/baselines/reference/varianceAnnotationValidation.types new file mode 100644 index 00000000000..285912f9c7b --- /dev/null +++ b/tests/baselines/reference/varianceAnnotationValidation.types @@ -0,0 +1,51 @@ +=== tests/cases/compiler/varianceAnnotationValidation.ts === +// Repro from #49607 + +// Variance annotation error expected + +interface Controller { + createAnimal: () => T; +>createAnimal : () => T + + run: (animal: T) => void; +>run : (animal: T) => void +>animal : T +} + +interface Animal { + run(): void; +>run : () => void + +}; + +class Dog implements Animal { +>Dog : Dog + + run() {}; +>run : () => void + + bark() {}; +>bark : () => void +} + +interface AnimalContainer { + controller: Controller; +>controller : Controller +} + +declare let ca: AnimalContainer; +>ca : AnimalContainer + +declare let cd: AnimalContainer; +>cd : AnimalContainer + +ca = cd; // Ok +>ca = cd : AnimalContainer +>ca : AnimalContainer +>cd : AnimalContainer + +cd = ca; // Error +>cd = ca : AnimalContainer +>cd : AnimalContainer +>ca : AnimalContainer + diff --git a/tests/cases/compiler/varianceAnnotationValidation.ts b/tests/cases/compiler/varianceAnnotationValidation.ts new file mode 100644 index 00000000000..4b8e054e146 --- /dev/null +++ b/tests/cases/compiler/varianceAnnotationValidation.ts @@ -0,0 +1,30 @@ +// @strict: true +// @declaration: true + +// Repro from #49607 + +// Variance annotation error expected + +interface Controller { + createAnimal: () => T; + run: (animal: T) => void; +} + +interface Animal { + run(): void; +}; + +class Dog implements Animal { + run() {}; + bark() {}; +} + +interface AnimalContainer { + controller: Controller; +} + +declare let ca: AnimalContainer; +declare let cd: AnimalContainer; + +ca = cd; // Ok +cd = ca; // Error