tsgo eagerly resolve the type of inferred function Expression
TSGO eagerly resolve the type of inferred function Expression. for simplicity the example shows how keyof TestInterface is resolved to never (but is wrong as TestInterface could be augmented), but the issue is not limited to just keyof empty interfaces. The eager resolution happens to both params and return types of the function.
This leads to a whole set of issues (for example const getClient = (config: Simplify<AxiosConfig>) => ,,, would resolve config to a object literal that also use some internal types in axios making getClient not portable.
if the function expression type is explicitly defined the problem does not appear.
Steps to reproduce
export interface TestInterface {}
export const inferredFunction = function (): Record<keyof TestInterface, string> {return {} as any}
export const explicitFunction: () => Record<keyof TestInterface, string> = function (): Record<keyof TestInterface, string {return {} as any}
export const inferredArrowFunction = (): Record<keyof TestInterface, string> => ({}) as any;
export const explicitArrowFunction: () => Record<keyof TestInterface, string> = () => ({}) as any;
export function functionDeclaration() :Record<keyof TestInterface, string> { return {} as any}
Behavior with [email protected]
export interface TestInterface {}
export declare const inferredFunction: () => Record<keyof TestInterface, string>;
export declare const explicitFunction: () => Record<keyof TestInterface, string>;
export declare const inferredArrowFunction: () => Record<keyof TestInterface, string>;
export declare const explicitArrowFunction: () => Record<keyof TestInterface, string>;
export declare function functionDeclaration(): Record<keyof TestInterface, string>;
Behavior with tsgo
export interface TestInterface {}
export declare const inferredFunction: () => Record<never, string>;
export declare const explicitFunction: () => Record<keyof TestInterface, string>;
export declare const inferredArrowFunction: () => Record<never, string>;
export declare const explicitArrowFunction: () => Record<keyof TestInterface, string>;
export declare function functionDeclaration(): Record<keyof TestInterface, string>;
This isn't enough info... in what context are you looking at this? Is this an output problem? what is the tsconfig?
@jakebailey : apologies I was having some connectivity issues and had to edit the issue multiple times. is this enough ?
note: i think that some other issues (like #2220) could be related to this
I think that linked issue is unrelated.
Are you referring to declaration emit for these? If so, then this is probably due to the fact that we don't have declaration emit reuse nodes at the moment.
This is pretty dubious to rely on, though; the emitter is well within its rights to notice that TestInterface has no props and therefore it has no keys...
@jakebailey i've shown that they are actually the same issue in a comment (https://github.com/microsoft/typescript-go/issues/2220#issuecomment-3614655373)
and no it's not just declaration emit. it's the checker too. point is, give a fully annotated function there should be no additional resolution done by tsgo, it should just copy as it is
They are "the same" in that they both involve the fact that declaration emit used to reuse nodes when possible, but currently does not. But the underlying cause is not the same, no.
apologies it's not clear to me what you mean with 'reusing nodes' as it's my first day looking at this project. i fully trust your expertise though, and I'm happy to follow your guidance and provide any useful info to get this sorted as the current behaviour is definitely unexpected.
Regarding the This is pretty dubious to rely on, though I mean, as long as typescript allows for interface augmentation / declaration merging, then , no the emitter has no right to ever resolve keyof an exported interface.
Regarding the This is pretty dubious to rely on, though I mean, as long as typescript allows for interface augmentation / declaration merging, then , no the emitter has no right to ever resolve keyof an exported interface.
And yet, that's the way it worked until somewhat recently! 😄
From my understanding, this issue prevents using tsgo with any SST v2 project. SST relies heavily on module augmentation for type-safe infrastructure access.
Pattern SST ships empty interfaces in sst/node/config:
export interface ConfigTypes {}
export interface ParameterResources {}
export declare const Config: ConfigTypes & ParameterTypes & SecretTypes;
At build time, SST generates .sst/types/index.ts with augmentations:
declare module "sst/node/config" {
export interface ConfigTypes {
APP: string;
STAGE: string;
}
export interface ParameterResources {
"API_URL": { value: string };
}
}
Result With tsc, Config.STAGE and Config.API_URL work correctly. With tsgo, these produce TS2339 errors because the interfaces are resolved as empty before augmentation is applied.
This affects all SST resource bindings (Config, Table, Bucket, Queue, etc.) - essentially making tsgo unusable for SST projects.