microsoft / microsoft/TypeScript

Brittle circularity inherited from factory-produced class

Abierto
#38,476 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Nadie ha tomado este issue todavía.

Needs Investigation
Lenguaje dominante
Go
Estrellas
111k
Forks
14.4k
Merge medio
1 d 19 h
PR fusionados (30 d)
117

Descripción

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!

Guía de contribución

Abrir la guía de contribución

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Línea de trabajo

Reproduce ambos ejemplos enlazados de TypeScript Playground con TypeScript 3.8.3, comparando el ejemplo circular que funciona con la versión genérica que informa de errores de circularidad. Empieza investigando cómo el comprobador de tipos gestiona los tipos recursivos mediante clases producidas por factories y tipos condicionales o mapeados. Se considera terminado cuando se determine si el diagnóstico es intencionado y, si no lo es, se cubra el comportamiento indicado con una prueba de regresión y se corrija.

Escrito por el modelo de indexación a partir del texto del issue.

Evaluación

Stack tecnológico
typescript
Área
compilers
Tipo de issue
Error
Dificultad
5/5
Tiempo estimado
Más de una semana
Estado de actividad
Estancado
Claridad
Bastante claro
Aptitud para principiantes
25/100

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.