microsoft / microsoft/TypeScript

Constrained generic types infer `never` on a function, even when the argument meets the constraints

Aperta
#44,389 3 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Needs Investigation
Lingua principale
Go
Stelle
111k
Fork
14.4k
Merge medio
1g 19h
PR unite (30g)
117

Descrizione

Bug Report

🔎 Search Terms
  • inference of generic types fails with typed argument
  • infer generic never
  • constrained generic infers never
🕗 Version & Regression Information
  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about 4.3.2
⏯ Playground Link

Playground link with relevant code

💻 Code
type ValidKey = string | number | symbol;

/* The class ultimately being built */
class Test<T, I extends ValidKey & keyof T> {
    idProp: I;

    constructor(idProp: I) {
        this.idProp = idProp;
    }
}

interface WithId<T, I extends ValidKey & keyof T> {
    idProp: I;
}

type IDableEntity<I extends ValidKey> = Record<I, ValidKey>;

class TestBuilder<T extends Record<ValidKey, any>> {
    id<I extends ValidKey & keyof T>(idProp: I): this & WithId<T, I> {
        return {
            ...this,
            "idProp": idProp
        };
    }
}

function testBuilder<T>(): TestBuilder<T> {
    return new TestBuilder<T>();
}

function build<
    T extends IDableEntity<I>,
    I extends ValidKey & keyof T>
(builder: TestBuilder<T> & WithId<T, I>): Test<T, I> {
    return new Test<T, I>(builder.idProp);
}

interface IDable {
    id: string;
}

const foo: TestBuilder<IDable> & WithId<IDable, 'id'> = testBuilder<IDable>().id('id');
const bar = build(foo);
/*
Argument of type 'TestBuilder<IDable> & WithId<IDable, "id">' is not assignable to parameter of type 'TestBuilder<IDableEntity<never>> & WithId<IDableEntity<never>, never>'.
  Type 'TestBuilder<IDable> & WithId<IDable, "id">' is not assignable to type 'WithId<IDableEntity<never>, never>'.
    Types of property 'idProp' are incompatible.
      Type 'string' is not assignable to type 'never'.ts(2345)
*/

This also fails if I create a new type CompleteTestBuilder<T extends IDableEntity<I>, I extends ValidKey & keyof T> = TestBuilder<T> & WithId<T, I> that build accepts as an argument, or if I define CompleteTestBuilder as its own interface extends TestBuilder<T> { idProp: I}.

A simpler version that does NOT fail:

function build2<T>(builder: TestBuilder<T>): TestBuilder<T> {
    return builder;
}

const baz: TestBuilder<IDable> = testBuilder<IDable>();
const qux = build2(baz); // qux: TestBuilder<IDable>

This version also does NOT fail:

function build3<T extends Record<ValidKey, any>>(builder: TestBuilder<T>): (t: T) => string {
    return (t: T) => 'boo';
}

const quux = build3(baz); // quux: (t: IDable) => string
🙁 Actual behavior

The build call fails with the error message:

Argument of type 'TestBuilder<IDable> & WithId<IDable, "id">' is not assignable to parameter of type 'TestBuilder<IDableEntity<never>> & WithId<IDableEntity<never>, never>'.
  Type 'TestBuilder<IDable> & WithId<IDable, "id">' is not assignable to type 'WithId<IDableEntity<never>, never>'.
    Types of property 'idProp' are incompatible.
      Type 'string' is not assignable to type 'never'.ts(2345)
🙂 Expected behavior

I would expect the build call to succeed, and types T and I to be inferred as IDable and "id", respectively. The argument type is already known and correct (it is even inferred correctly - the type assignment for foo is just to show that the type is correct). The types on the function should be able to match.

Guida per i contributori

Apri la guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Direzione di ricerca

Inizia dalla riproduzione collegata in Playground e dal comportamento di inferenza generica nella funzione build. Traccia come i vincoli su T e I producano never, quindi verifica che la chiamata build(foo) abbia esito positivo e inferisca T come IDable e I come "id" senza compromettere gli esempi più semplici.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
typescript
Ambito
compilers
Tipo di issue
Bug
Difficoltà
5/5
Tempo stimato
Più di una settimana
Stato di attività
Ferma
Chiarezza
Abbastanza chiara
Idoneità per principianti
30/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.