microsoft / TypeScript

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

`@type {const}` produces namespace in .d.ts instead of normal object type #56789

Open jorgecasar opened 10 months ago

jorgecasar commented 10 months ago

πŸ”Ž Search Terms

@type {const} const definition types

πŸ•— Version & Regression Information

⏯ Playground Link

https://www.typescriptlang.org/play?target=99&jsx=0&ts=5.3.2&filetype=ts#code/KYDwDg9gTgLgBAYwgOwM7wLYE8DCL1wC8cA9AFRlwACMWYwcA3kmjAL5xklMCwAUAEgAZhAgAuOAHIARgEMokgDT9hAV1UTGK4aIkz5klW35sA3PyA

πŸ’» Code

// Typescript file
export const myConst = /** @type {const} */ {
    foo: 'bar',
    fuu: {
        foo: 'bar'
    }
};
// Javascript file
export const myConst = /** @type {const} */ {
    foo: 'bar',
    fuu: {
        foo: 'bar'
    }
};

πŸ™ Actual behavior

// .D.TS from Typescript file
export declare const myConst: {
    foo: string;
    fuu: {
        foo: string;
    };
};
// .D.TS from Javascript file
export namespace myConst {
    let foo: string;
    namespace fuu {
        let foo_1: string;
        export { foo_1 as foo };
    }
}

πŸ™‚ Expected behavior

Same output in both cases. Typescript file output is the correct one.

Additional information about the issue

No response

fatcerberus commented 10 months ago

The @type {const} doesn't seem to be relevant - the .js produces the same output whether it's present or not.

Note that TS ignores type info in JSDoc comments when processing .ts files.

jorgecasar commented 9 months ago

Form typescript documentation: You can even cast to const just like TypeScript:

let one = /** @type {const} */(1);

I guess, same code in Typescript and Javascript should output same Javascript and same definition types files. And even more when you are typing Javascript code with jsdoc as Typescript supports

RyanCavanaugh commented 9 months ago

In TS files, you have to use TS syntax for type annotations / assertions. Producing identical .d.ts output from .ts and .js files is not an intended invariant.

jorgecasar commented 9 months ago

Then, how can I annotate the JS file to get a constant declaration? I'm trying to expose types of a library to be able using it with Typescript and this is not what a Typescript developer expect for a const:

export namespace myConst {
    let foo: string;
    namespace fuu {
        let foo_1: string;
        export { foo_1 as foo };
    }
}

In the other hand, based on your answer if I would like to move a project from js to ts could generate unexpected results during the migration. I thought that Typescript was a superset of Javascript, and same code should producing same output.

RyanCavanaugh commented 9 months ago

The are sort of two issues being implied here:

sandersn commented 9 months ago

I think there are two smaller repro pairs that are relevant.

Equivalence of 'as const':

export const a_ts = { foo: 'bar' } as const
export const a_js = /** @type {const} */({ foo: 'bar' })

Both a_ts and a_js should have type { readonly foo: 'bar' } not { foo: string }. Quick info shows that they do, so there's no bug here.

Equivalence of const object-literal:

export const b_ts = { foo: 'bar' }
export const b_js = { foo: 'bar' }

Both b_ts and b_js should generate export declare const b_ts: { foo: string } But b_js generates

export namespace b_js {
  let foo: string
}

This is the bug; the rest of the export { foo_1 as foo } in the longer examples are renaming shenanigans that are required for a namespace (as far as I know off the top of my head).

I'm pretty sure this is a result of const x = { object: "literal" } being tagged as an assignment declaration, so the fix is likely either a design change in the binder or a workaround in declaration emit (where the workaround is essentially "this looks special but there's only one declaration, so treat it normally after all").