microsoft / microsoft/TypeScript
instanceof narrowing should preserve generic types from super to child type
Nadie ha tomado este issue todavía.
- Lenguaje dominante
- Go
- Estrellas
- 111k
- Forks
- 14.4k
- Merge medio
- 1 d 19 h
- PR fusionados (30 d)
- 117
Descripción
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!
Guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Línea de trabajo
No se nombran archivos ni pruebas. Empieza reduciendo los ejemplos de Parent/Child, Fetcher y Dependency, y rastreando el narrowing de instanceof de TypeScript junto con la inferencia de tipos genéricos. Se considera terminado cuando los argumentos genéricos estrechados se conservan donde pueden inferirse, y los casos de unión documentados se comportan de forma coherente.
Escrito por el modelo de indexación a partir del texto del issue.
Evaluación
- Stack tecnológico
- typescript
- Área
- compilers
- Tipo de issue
- Nueva funcionalidad
- Dificultad
- 5/5
- Tiempo estimado
- Más de una semana
- Estado de actividad
- Estancado
- Claridad
- Bastante claro
- Aptitud para principiantes
- 30/100