-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Interfaces and types that use conditional types and inheritance no longer assignable #32608
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
Comments
Is this the simplest possible repro? |
I can remove the extra layer of creating a type to pull property names from via keyof, but still see the error. |
This issue sounded related, but I didn't see the error go away when I was using the under dev typescript version: |
@ahejlsberg this looks like a possible incorrect variance measurement. |
I think the issue here is with the simultaneous use of type GetPropertyNamesOfType<T, K extends number> = {
[PropertyName in keyof T & string]: [T[PropertyName], PropertyName][K]
}[keyof T & string];
type GetAllPropertiesOfType<T, K extends number> = Pick<T, GetPropertyNamesOfType<Required<T>, K> & keyof T>;
export type IRequestB<T extends object, K extends number> = {
getValue(name: keyof GetAllPropertiesOfType<T, K>): string;
getObject<ObjectType extends object = never>(name: keyof GetAllPropertiesOfType<T, K>): IRequestB<ObjectType, K>;
}
interface Base {
BaseString: string;
}
interface Derived extends Base {
DerivedString: string;
}
function main(b: IRequestB<Derived, 1>): void {
const b1: IRequestB<Base, 1> = b;
} |
The intent in my example was to pull the property names for a property that matches a given data type. The getValue method should only allow specifying a "name" of a property on T that is of type string (i.e. BaseNumber and BaseObject should be excluded). It is also filtering out property names that aren't of type string, but my example doesn't have any property names that aren't strings. The quoted example above looks to be a different way to express Extract<keyof T, string>. |
It is also a need for this to work with a list of objects of a given type. My current workarounds don't seem to allow me to not produce an error.
|
Here's a cough simplified demo that the variance is measured incorrectly relative to a manual instantiation. interface Base {
BaseString: string;
BaseObject: Base;
}
interface Derived extends Base {
DerivedString: string;
DerivedObject: Base;
}
type GetPropertyNamesOfType<T, RestrictToType> = {
[PropertyName in keyof T]: T[PropertyName] extends RestrictToType ? PropertyName : never
}[keyof T];
export type IRequestC<T> = {
getObject: (name: GetPropertyNamesOfType<T, object>) => void;
}
export type IRequestDerived = {
getObject: (name: GetPropertyNamesOfType<Derived, object>) => void;
}
export type IRequestBase = {
getObject: (name: GetPropertyNamesOfType<Base, object>) => void;
}
function concreteContravariant(base: IRequestBase) {
let derived: IRequestDerived = base;
}
function concreteCovariant(derived: IRequestDerived) {
let base: IRequestBase = derived;
}
function genericContravariant(base: IRequestC<Base>): void {
const derived: IRequestC<Derived> = base;
}
function genericCovariant(derived: IRequestC<Derived>): void {
const base: IRequestC<Base> = derived;
} |
As check types are related bivariantly I would expect that variance measuring going wrong for the check type would make the measured types more assignable, rather than less. (False-negative) type Alias<T> = [T] extends [number] ? true : false;
interface Foo<T> {
x: Alias<T>;
}
const a: Foo<number> = { x: true };
const b: Foo<number | boolean> = a; // no error
const shouldBeFalse: false = b.x;
const c: Foo<number | boolean> = { x: false };
const d: Foo<number> = c; // no error
const shouldBeTrue: true = d.x; Conversely, the extends types is invariant so that usually makes measured types less assignable, rather than more. (False-positive). type Alias2<T> = [number] extends [T] ? true : false;
interface Foo2<T> {
x: Alias2<T>;
}
const a: Foo2<number> = { x: true };
const b: Foo2<number | boolean> = a; // error
const shouldbeTrue: true = b.x; I think trying to get any meaningful measurements from a conditional type is hard. When the true and false types are unrelated then measurements are just wrong because the conditional type cannot be an order preserving function of its outer type parameters. My concern is that removing measurements for conditional types would be a breaking change for many people that used |
I just wanted to check in on this to see if there was an idea on when this would be addressed. I am trying to determine whether I should look at alternatives for my API. |
This looks to be the same issue as #32674. When a type parameter appears in both a co- and contra-variant position within a type, as in |
TypeScript Version: 3.6.0-dev.20190727
Search Terms: conditional, generic, inheritance, variance
Code
Prior to the --strictFunctionTypes compiler flag in the strict suite, this previously worked fine.
As the result of a suggestion in Issue 28671 the interface was switch to a type. This worked until TypeScript version 3.5 where the assignment again started to fail when inheritance was involved with T.
With some changes I got it to work by switching around the allowed property name list:
Expected behavior:
I would expect b to be assignable to b1 in the example above to be able to work with the subset of properties that are specific to the base type.
Actual behavior:
The assignment doesn't work or I need to jump through hoops to make it work.
I don't know if I'm just trying to limp by staying one step ahead of type checker improvements that will again cause me to make changes or possibly disallow me from accomplishing what I'm looking for.
I'm also not sure about the inconsistency in the need to update the
getValue
method versusgetObject
.Playground Link:
Demo
Related Issues:
Issue 28671
Issue 24190
The text was updated successfully, but these errors were encountered: