microsoft / microsoft/TypeScript
instanceof narrowing should preserve generic types from super to child type
Nessuno ha ancora preso questa issue.
- Lingua principale
- Go
- Stelle
- 111k
- Fork
- 14.3k
- Merge medio
- 2g 4h
- PR unite (30g)
- 132
Descrizione
Search Terms
- instanceof generic
- narrow instanceof
Suggestion
The following code has a rather unexpected behavior today:
class Parent<T> {
x: T;
}
class Child<S> extends Parent<S> {
y: S;
}
function example(obj: Parent<number>) {
if (obj instanceof Child) {
const child = obj;
}
}
The narrowed type that child gets is Child<any>.
I'm requesting that the type instead gets narrowed to Child<number>, which is what I originally assumed would happen.
My proposal is, in general, to infer the type arguments to the narrowed-to class type whenever they can be determined from the original type.
Precise Behavior
Consider the following code today:
const x: P = ...;
if (x instanceof C) {
x;
}
Today, if C is a generic class, x gets the narrowed type C<any, any, any, ...>; otherwise it just gets the type C.
My proposal is to consider the following piece of code, and use it to inform
Imagine that C has a no-argument constructor. Then the following piece of code is valid today:
function onlyP(c: P) { ... }
onlyC(new C());
in order to make it valid, the compiler infers type arguments for C that make it into a subtype of P. My proposal is to use the same strategy to infer the type arguments for C in an instanceof narrowing.
Consider the following examples today, and their corresponding instanceof narrowings:
function ex1(x: Parent<string>) { }
ex1(new Child()); // inferred arguments: <string>
function ex2(x: Parent<number | string>) { }
ex2(new Child()); // inferred arguments: <number | string>
function ex3(x: Parent<number> | string) { }
ex3(new Child()); // inferred arguments: <number>
function ex4(x: Parent<number> | Parent<string>) { }
ex4(new Child()); // inferred arguments: Child<number | string>
// Note: the above errors, because it Child<number|string> actually fails to be a subtype of
// Parent<number> | Parent<string>.
// We can either choose to infer Child<any> in this case, or use the (incorrect, but more-precise)
// inference Child<number | string>.
Examples
The original use-case I had in mind was roughly the following:
abstract class Obtainer<T> {
__phantom: T = null as T;
}
abstract class Fetcher<T> extends Obtainer<T> {
public abstract fetch(): Promise<T>;
}
abstract class Dependency<T, D> extends Obtainer<T> {
public abstract dependencies(): Obtainer<D>;
public abstract compute(got: D): T;
}
async function obtain<T>(obtainer: Obtainer<T>): Promise<T> {
if (obtainer instanceof Fetcher) {
// obtainer: Fetcher<T>
// currently, it's a Fetcher<any>
return await obtainer.fetch();
}
if (obtainer instanceof Dependency) {
// obtainer: Dependency<T, any>
// currently, it's a Dependency<any, any>
const dependencies = obtainer.dependencies();
return obtainer.compute(await obtain(dependencies));
}
throw new Error("not implemented");
}
(note: there's still one extraneous any in the above, since the D parameter cannot be inferred)
Checklist
My suggestion meets these guidelines:
- This wouldn't be a breaking change in existing TypeScript/JavaScript code
- This is a breaking change. However, I think it could only really break code that was already broken. If really necessary, it could be put behind a new
--strictGenericNarrowingflag.
- This is a breaking change. However, I think it could only really break code that was already broken. If really necessary, it could be put behind a new
- This wouldn't change the runtime behavior of existing JavaScript code
- No change to runtime; just inferred generic types.
- This could be implemented without emitting different JS based on the types of the expressions
- [ x This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
- This feature would agree with the rest of TypeScript's Design Goals.
- I think this is an easy win for a more sound type system without any negative impact
Also, I am interested in contributing this change if it's approved!
Guida per i contributori
Apri la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Direzione di ricerca
Non vengono indicati file o test. Inizia riducendo gli esempi di Parent/Child, Fetcher e Dependency e tracciando il narrowing di instanceof di TypeScript insieme all'inferenza dei tipi generici. Il lavoro è completato quando gli argomenti generici ristretti vengono preservati dove possono essere dedotti e i casi di unione documentati si comportano in modo coerente.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Valutazione
- Stack tecnologico
- typescript
- Ambito
- compilers
- Tipo di issue
- Funzionalità
- Difficoltà
- 5/5
- Tempo stimato
- Più di una settimana
- Stato di attività
- Ferma
- Chiarezza
- Abbastanza chiara
- Idoneità per principianti
- 30/100