microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.77k stars 12.46k forks source link

Non-exported recursive type alias is inlined until it falls back to any in declarations #58696

Open dragomirtitian opened 4 months ago

dragomirtitian commented 4 months ago

🔎 Search Terms

declarations recursive types

🕗 Version & Regression Information

⏯ Playground Link

Playground Link

💻 Code

// @declaration: true
// @filename: utils.ts 

type Recursive<T>  =  T extends Array<infer V>? Recursive<V>: T;

export const f = <T>(): Recursive<T> => { return null! } 

// @filename: index.ts 
import { f } from './utils';
export const a = f;

🙁 Actual behavior

In declarations, the type for a is printed as a: () => T extends (infer V)[] ? V extends (infer V)[] ? V extends (infer V)[] ? V extends (infer V)[] ? V extends (infer V)[] ? V extends (infer V)[] ? V extends (infer V)[] ? V extends (infer V)[] ? V extends (infer V)[] ? V extends (infer V)[] ? V extends (infer V)[] ? any : V : V : V : V : V : V : V : V : V : V : T;

🙂 Expected behavior

Recursive can't be represented without falling back to any (as seen above) so it should just be an error instead of silently to any

Additional information about the issue

Related to #55832

jakebailey commented 4 months ago

Generally, I feel like it'd be even better to always emit some sort of declaration error whenever declaration emit synthesizes an any out of nowhere...

dragomirtitian commented 4 months ago

I'll open a PR after 5.5. I tried locally and there are several places that now have errors, but in my opinion they are all good. Maybe we have just a carveout if someone is not using noImplicitAny (although hopefully nobody is actually doing that 😅)

ghost commented 4 months ago

I think this is the same issue I'm having, but I'm not sure.

Problem description within... ```ts // Exported from `@mylib/dev/jrfs`: export interface FileOf { data: D; meta: M; } export type FileTypes = Record; // Exported from `@mylib/dev/features/db`: export interface DbDesign { db: DbModel; } export interface DbDesignFileMeta { test: boolean; } export type DbDesignFile = FileOf; ``` In the app project which imports from `@mylib`, when I use the `DbDesignFile` type exported from `@mylib/dev`, then VS Code shows the wrong type (`const data: FileOf`) in the tooltip and autocomplete doesn't work and it acts like the found `data` is `any`... ```ts import { DbDesignFile } from "@mylib/dev/features/db"; interface ProjectFileTypes { db: DbDesignFile; } // Try it out: class Yada> { async findTypes( type: K, ): Promise< Array<{ node: any; data: Required["data"]; }> > { return []; } } const yada = new Yada(); const nodes = await yada.findTypes("db"); for (const { node, data } of nodes) { // TODO: Figure out why `data` is treated like `any` here: console.log("FOUND", node.name, data); } ``` However, if I re-define the DbDesignFile type alias in my app project then `data` from the `yada.findTypes` call above correctly shows and autocompletes type `DbDesign`... ```ts import { FileOf } from "@mylib/dev/jrfs"; import { DbDesign, DbDesignFileMeta } from "@mylib/dev/features/db"; type DbDesignFile= FileOf; interface ProjectFileTypes { db: DbDesignFile; } ```

UPDATE: I figured out my own issue - my library's type declarations were being created with import type {...} from "@/jrfs"; because I don't have anything in place to fix tsc created alias import paths. Once I changed those to use relative import paths it worked. So, never mind, sorry for the noise!