Skip to content

TypeChecker: Symbol.getDeclarations() returns undefined declarations when used with complex generic types #61697

Closed as not planned
@eddeee888

Description

@eddeee888

πŸ”Ž Search Terms

"getDeclarations", "ts.Declaration", "returns undefined"

πŸ•— Version & Regression Information

  • I was unable to test this on prior versions because version 4 does not support ts.createProgram function

⏯ Playground Link

https://github.com/eddeee888/typescript-type-getdeclarations-bug

πŸ’» Code

// shared.ts
type Generated<S> = ColumnType<S, S | undefined, S>;
type ColumnType<
  SelectType,
  InsertType = SelectType,
  UpdateType = SelectType
> = {
  readonly __select__: SelectType;
  readonly __insert__: InsertType;
  readonly __update__: UpdateType;
};
type DrainOuterGeneric<T> = [T] extends [unknown] ? T : never;
type IfNotNever<T, K> = T extends never ? never : K;
type SelectType<T> = T extends ColumnType<infer S, any, any> ? S : T;
type NonNeverSelectKeys<R> = {
  [K in keyof R]: IfNotNever<SelectType<R[K]>, K>;
}[keyof R];

// 1. Selectable is a complex generic type
// It strips all generics from the properties of an interface/type
export type Selectable<R> = DrainOuterGeneric<{
  [K in NonNeverSelectKeys<R>]: SelectType<R[K]>;
}>;

export interface BookTable {
  id: Generated<number>;
  isbn: string;
  next_book_in_series_id: number | null;
  previous_book_in_series_id: number | null;
}

// source-error.ts
import type { BookTable, Selectable } from "./shared";

/**
 * Wrapping in `Selectable` results in `.getDeclarations` returns `undefined`
 */
export type BookMapper = Selectable<BookTable>;

// main.ts
import * as ts from "typescript";

const program = ts.createProgram([process.env.SOURCE || ""], {});

const checker = program.getTypeChecker();

for (const sourceFile of program.getSourceFiles()) {
  if (!sourceFile.isDeclarationFile) {
    ts.forEachChild(sourceFile, visit);
  }
}

function visit(node: ts.Node): void {
  if (
    ts.isTypeAliasDeclaration(node) &&
    node.name &&
    node.name.text === "BookMapper"
  ) {
    const type = checker.getTypeAtLocation(node);
    console.dir(
      {
        properties: type.getProperties().map((prop) => {
          return {
            propName: prop.getName(),
            declarations: prop.getDeclarations(), // this should get the type of each `BookTable`'s prop, but it is returning undefined when `Selectable` is used
          };
        }),
      },
      { depth: 4 }
    );
  }
}

πŸ™ Actual behavior

prop.getDeclarations() in main.ts should return the type of each declaration

πŸ™‚ Expected behavior

prop.getDeclarations() in main.ts returns undefined

Additional information about the issue

It works fine if:

  • Selectable is not used to wrap BookTable
  • A simpler generic is used to wrap BookTable e.g. type Test<T> = T & { __test: boolean }

I can see BookMapper's final types being recognised correctly by Language Server (image blow), not sure if I'm missing something obvious.

Image

Metadata

Metadata

Assignees

No one assigned

    Labels

    QuestionAn issue which isn't directly actionable in code

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions