microsoft / microsoft/TypeScript

Brittle circularity inherited from factory-produced class

Offen
#38,476 0 Kommentare 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Dieses Issue hat noch niemand übernommen.

Needs Investigation
Vorherrschende Sprache
Go
Sterne
111k
Forks
14.4k
Ø Merge
1 T. 19 Std.
Gemergte PRs (30 T.)
117

Beschreibung

TypeScript Version: 3.8.3

Search Terms: extend, from, circular, constructor, class, factory

The following works like a charm. I'm able to define a circular relationship between RecordShape and ArrayShape. This is wonderful!

class ArrayShape {
  constructor(public of: any) { }
}

namespace ArrayShape {
  export function of(of: any) {
    return class extends ArrayShape {
      constructor() {
        super(of);
      }
    }
  }
}

class RecordShape {
  constructor(public of: object) { }
}

namespace RecordShape {
  export function of(of: any) {
    return class extends RecordShape {
      constructor() {
        super(of);
      }
    }
  }
}

class CycleNodeA extends RecordShape.of(() => CycleNodeB) {}
class CycleNodeB extends ArrayShape.of(() => CycleNodeA) {}

However, this starts to break down as I make it more generic. In the example below, I begin to narrow down the type passed into the ArrayShape and RecordShape constructors. The result is––once again––circularity errors :/ here is the playground.

function TypeOnly<T>() {
  return (undefined as any) as T;
}

// the base from which `ArrayShape` and `RecordShape` extend
abstract class Shape<N extends string, T> {
  abstract readonly name: N;
  abstract readonly type: T;
}

// extracts the `T` from a given shape
type DecodeShape<S extends Shape<string, any>> =
  S extends Shape<string, infer T>
    ? T
    : never;

// for referencing shapes which aren't yet defined
type PointerShape = (() => Shape<string, any>);

// utils for treating shape and pointer shape types as if they are the same
type ShapeLike = Shape<string, any> | PointerShape;
type NormalizeShape<S extends ShapeLike> =
  S extends PointerShape
    ? S extends (() => infer SS) ? SS : never
    : S;
type DecodeShapeLike<S extends ShapeLike> = DecodeShape<NormalizeShape<S>>;

class ArrayShape<T extends ShapeLike> extends Shape<"array", DecodeShapeLike<T>[]> {
  readonly name = "array";
  readonly type = TypeOnly<DecodeShapeLike<T>[]>();

  constructor(public elementShape: T) {
    super()
  }
}

namespace ArrayShape {
  export function of<T extends ShapeLike>(elementShape: T) {
    return class extends ArrayShape<T> {
      constructor() {
        super(elementShape);
      }
    }
  }
}

class RecordShape<T extends Record<string, ShapeLike>> extends Shape<"record", {
  [K in keyof T]: DecodeShapeLike<T[K]>;
}> {
  readonly name = "record";
  readonly type = TypeOnly<{
    [K in keyof T]: DecodeShapeLike<T[K]>;
  }>();

  constructor(public fieldShapes: T) {
    super();
  }
}

namespace RecordShape {
  export function of<T extends Record<string, ShapeLike>>(fieldShapes: T) {
    return class extends RecordShape<T> {
      constructor() {
        super(fieldShapes);
      }
    }
  }
}

const CycleNodeA = RecordShape.of({
  b: () => new CycleNodeB()
}) {}
class CycleNodeB extends ArrayShape.of(() => new CycleNodeA()) {}

If the circularity error turns out to be the intended behavior, apologies––I can post on StackOverflow instead. Otherwise, any help would be greatly appreciated! Thank you!

Beitragsleitfaden

Beitragsleitfaden öffnen

Erste Schritte

  1. Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
  3. Forke das Repository und arbeite in einem Branch.
  4. Öffne einen Pull Request, der die Issue-Nummer nennt.

Rechercherichtung

Reproduziere beide verlinkten TypeScript Playground-Beispiele mit TypeScript 3.8.3 und vergleiche das funktionierende zirkuläre Beispiel mit der generischen Version, die Zirkularitätsfehler meldet. Untersuche zunächst, wie der Type Checker rekursive Typen über von Factorys erzeugte Klassen sowie Conditional Types oder Mapped Types behandelt. Als abgeschlossen gilt die Untersuchung, wenn festgestellt wurde, ob die Diagnose beabsichtigt ist, und, falls nicht, das gemeldete Verhalten mit einem Regressionstest abgedeckt und korrigiert wurde.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Bewertung

Tech-Stack
typescript
Bereich
compilers
Issue-Typ
Bug
Schwierigkeit
5/5
Geschätzter Aufwand
Über eine Woche
Aktivitätsstatus
Veraltet
Klarheit
Größtenteils klar
Anfängerfreundlichkeit
25/100

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.