Skip to content

Bloomberg feedback for 7.0 beta #63824

Closed
Closed

Description

This document summarizes observed tsgo differences compared with tsc in the Bloomberg TypeScript code base. The differences in output are numerous but they are either fixed or they are small enough not to be significant.

There are a small number of type checking differences that we reported previously that still impact the beta as well. These are included for completeness, but they impact a small number of packages so migration should not be held back by these.

All issues affect a small number of packages.

Summary

# Area Severity Issue Classification GitHub ticket
1 Declaration emit Medium Extra _N suffix on object-literal method type parameters Bug #3968
2 Declaration emit Medium Methods are not marked readonly in as const assertions Bug #3959
3 Declaration emit Medium Emojis are emitted as Unicode escapes Bug #3966
4 Declaration emit Medium JSDoc preserved from removed import Bug #3955
5 Declaration emit Medium JSDoc removed on private async functions Bug #3954
6 Declaration emit Low Expando properties on functions are not merged into a single namespace Intentional #3970
7 Declaration emit Low Parentheses added around typeof access Bug #3961
8 Declaration emit Low Empty JSDoc /***/ is removed Bug #3956
9 Declaration emit Low Getters/setters preserved even when they do not need to be Intentional #3958
10 Declaration emit Low Quoted properties are sometimes preserved for one level Intentional #3957
11 Declaration emit Low Removal of undefined from optional fields Improvement
12 Declaration emit Low Removal of trailing comma in destructuring parameters Improvement
13 Declaration emit Low String literal delimiters are preserved Improvement
14 JS emit Medium Assertion causes JSX spread object properties to emit on one line Bug #3971
15 JS emit Low Parentheses removed around TS instantiation expression emit Improvement
16 JS emit Low JSX property names no longer quoted unnecessarily Improvement
17 Source maps Low Better mapping of identifiers in some cases Improvement
18 Type checking Low ts-ignore et all cause new errors Won't Fix
19 Type checking Low New error when inferring return type from similar object types with promises Won't Fix #1743
20 Type checking Low this is no longer any in functions of object literal assigned to prototype Won't Fix #1782
21 Type checking Low Duplicate identifier error in type with computed property names Improvement #1740
22 Type checking Low Inference fails form unions of arrays of different depths (T[] | T[][]) Bug #1789
23 Type checking Low Duplicate identifier reported between class decl and function prop assignment Won't Fix #1783

Declaration: extra _N suffix on object-literal method type parameters

GitHub ticket: #3968
Repro: Playground

In object literals, function type parameters get an extra _N suffix even if they do not need it.

Input:

export const o = {
    foo<M extends string>(): void { },
    bar<M extends string>(): void { },
};

tsc:

export declare const o: {
    foo<M extends string>(): void;
    bar<M extends string>(): void;
};

tsgo:

export declare const o: {
    foo<M extends string>(): void;
    bar<M_1 extends string>(): void;
};

Declaration: methods are not marked readonly in as const assertions

GitHub ticket: #3959
Repro: Playground

Methods are not marked as readonly in as const assertions.

Input:

export const OptionUtil = {
    makeTimeout(timeout: number) {
        return RspUtil.makeTimeout(timeout);
    },
} as const;

tsc:

export declare const OptionUtil: {
    readonly makeTimeout: (timeout: number) => any;
};

tsgo:

export declare const OptionUtil: {
    makeTimeout(timeout: number): any;
};

Declaration: emojis are emitted as Unicode escapes

GitHub ticket: #3966
Repro: Playground

Emojis are printed as Unicode escapes instead of original characters.

Input:

export const IconEmojis = {
    alert_low: "⚠️",
} as const

tsc:

export declare const IconEmojis: {
    readonly alert_low: "⚠️";
};

tsgo:

export declare const IconEmojis: {
    readonly alert_low: "\u26A0\uFE0F";
};

Declaration: JSDoc preserved from removed import

GitHub ticket: #3955
Repro: Playground

JSDoc is preserved from a removed import statement.

Input:

/**
 * Some random docs not related to foo
 */
/* ignore next */
import * as x from './index.js';
export const foo = 1;

tsc:

export declare const foo = 1;

tsgo:

/**
 * Some random docs not related to foo
 */
export declare const foo = 1;

Declaration: JSDoc removed on private async functions

GitHub ticket: #3954
Repro: Playground

JSDoc is removed from private async functions.

Input:

export class Test {
    /**
     * Non Async function
     */
    private nonAsyncFunction(): void {
    }

    /**
     * Async function
     */
    private async asyncFunction(): Promise<void> {
    }
}

tsc:

export declare class Test {
    /**
     * Non Async function
     */
    private nonAsyncFunction;
    /**
     * Async function
     */
    private asyncFunction;
}

tsgo:

export declare class Test {
    /**
     * Non Async function
     */
    private nonAsyncFunction;
    private asyncFunction;
}

Declaration: expando properties on functions are not merged into a single namespace

GitHub ticket: #3970
Repro: Playground
Resolution: Intentional

Expando properties on functions are not merged into a single namespace.

Input:

export function not() {}
not.negate = () => {};
not.toString = () => ' ~ ';

tsc:

export declare function not(): void;
export declare namespace not {
    var negate: () => void;
    var toString: () => string;
}

tsgo:

export declare function not(): void;
export declare namespace not {
    var negate: () => void;
}
export declare namespace not {
    var toString: () => string;
}

Declaration: parentheses added around typeof access

GitHub ticket: #3961
Repro: Playground

Parentheses are always added around typeof, diverging from source.

Input:

const C = { A: 1 }
export type C = typeof C[keyof typeof C];

tsc:

export type C = typeof C[keyof typeof C];

tsgo:

export type C = (typeof C)[keyof typeof C];

Declaration: empty JSDoc /***/ is removed

GitHub ticket: #3956
Repro: Playground

Empty JSDoc comment (/***/) is removed.

Input:

/***/
export const foo = 1;

tsc:

/***/
export declare const foo = 1;

tsgo:

export declare const foo = 1;

Declaration: getters/setters preserved even when they do not need to be

GitHub ticket: #3958
Repro: Playground
Resolution: Intentional

get/set are preserved even when they do not have to be preserved.

Input:

export const u = {
    get foo(): string | null {
        return ""
    },
    set foo(val: string | null) {
    },
}

tsc:

export declare const u: {
    foo: string | null;
};

tsgo:

export declare const u: {
    get foo(): string | null;
    set foo(val: string | null);
};

Declaration: quoted properties are sometimes preserved for one level

GitHub ticket: #3957
Repro: Playground
Resolution: Intentional

Quoted properties of inferred types are preserved starting one level down (they are not preserved at the top level of a type).

Input:

function createSlice<T>(o: T) {
    return o
}
export const platformSlice = createSlice({
    "query": {
        "search" : "",
        "user": ""
    },
});

tsc:

export declare const platformSlice: {
    query: {
        search: string;
        user: string;
    };
};

tsgo:

export declare const platformSlice: {
    query: {
        "search": string;
        "user": string;
    };
};

Declaration: removal of undefined from optional fields

Repro: N/A

In some reprinted types, undefined is removed from the field type. This seems better since the originating field does not have undefined in the type.

tsc:

sessionId?: string | undefined;

tsgo:

sessionId?: string;

Declaration: removal of trailing comma in destructuring parameters

Repro: Playground

This seems like an improvement, even though it does diverge from source code a bit. The destructuring is reprinted on a single line anyway, so removing the , makes sense.

Input:

export function fn({
    data,
}: {
    data: string;
}) {}

tsc:

export declare function fn({ data, }: {
    data: string;
}): void;

tsgo:

export declare function fn({ data }: {
    data: string;
}): void;

Declaration: string literal delimiters are preserved

Repro: N/A

In inferred types string literals are now more frequently preserved in output with the source delimiter.

Input:

readonly COMPLETE: 'COMPLETE';

tsc:

readonly COMPLETE: "COMPLETE";

tsgo:

readonly COMPLETE: 'COMPLETE';

JS emit: assertion causes JSX spread object properties to emit on one line

GitHub ticket: #3971
Repro: Playground

The presence of an assertion results in properties being emitted on the same line in JSX spread.

Input:

<EntryTextDialog
    {...{
        foo: 1,
        bar: 2 as any,
        baz: 3,
    }}
/>

tsc:

_jsx(EntryTextDialog, { foo: 1,
    bar: 2,
    baz: 3 });

tsgo:

_jsx(EntryTextDialog, { foo: 1, bar: 2, baz: 3 });

JS emit: parentheses removed around TS instantiation expression emit

Repro: Playground

Difference: parentheses are removed in JS around a TS instantiation expression.

Input:

const UserListGrid = ManagedListGrid<TriageMember>;

tsc:

const UserListGrid = (ManagedListGrid);

tsgo:

const UserListGrid = ManagedListGrid;

JS emit: JSX property names no longer quoted unnecessarily

Repro: Playground

Improvement: properties are no longer quoted unnecessarily in JSX.

Input:

const c = <Comp $backButton="" ></Comp>

tsc:

const c = _jsx(Comp, { "$backButton": "" });

tsgo:

const c = _jsx(Comp, { $backButton: "" });

Source maps: better mapping of identifiers in some cases

Repro: N/A

We have noticed that in some cases identifiers seem to be mapped correctly now where before they were mapped to empty spans. Hard to create a self contained example.


Type checking: ts-ignore et all cause new errors

Repro: N/A

We have some code bases that migrated to TS by relying heavily on ts-ignore and ts-expect-errror. These code bases now have all sorts of new errors. We didn't report any of these specifically as we generally consider suppression to not reliably work between versions anyway. This are caused by different erorr spans in some cases, but we did not investgate to make sure this is the only reason


Type checking: new error when inferring return type from similar object types with promises

GitHub ticket: #1743
Repro: Playground
Resolution: Won't fix (not planned)

When returning similar object types through nested promises, tsgo can report an error where tsc accepts the code. The issue was marked as a type-ordering difference, and the team closed it as not planned.

Input:

function getInfo(): Promise<{
    id?: string;
    history?: string[];
}> {
    return Promise.resolve().then(() => {
        if (Math.random() > 0.5) {
            return Promise.resolve().then(() => {
                return {
                    id: "1",
                    history: ["1"],
                };
            });
        } else {
            return Promise.resolve({
                history: ["1"],
            });
        }
    });
}

tsc:

No error.

tsgo:

error TS2345 on the callback passed to Promise.then(...)

Type checking: this is no longer any in functions of object literal assigned to prototype

GitHub ticket: #1782
Repro: Playground
Resolution: Won't fix

For object literals assigned to .prototype, tsc has a special-case where this becomes any. tsgo instead types this as the containing object shape, which can surface new property errors.

Input:

function Stream() {
}

Stream.prototype = {
    endOfStream: function (): boolean {
        return !this.tokens.length;
    },
};

tsc:

No error (`this` is `any`).

tsgo:

Error on `this.tokens` because `this` is typed as `{ endOfStream: () => any; }`.

Type checking: duplicate identifier error in type with computed property names

GitHub ticket: #1740
Repro: Playground
Resolution: Improvement

tsgo reports duplicate identifiers for repeated computed property names in type literals, while tsc allows this specific case.

Input:

type X = {
    ["A"]: string
    ["A"]: string
}

tsc:

No error.

tsgo:

Duplicate identifier error.

Type checking: inference fails from unions of arrays of different depths (T[] | T[][])

GitHub ticket: #1789
Repro: Playground
Inference from T[] | T[][] can fail in tsgo due to type-ordering behavior when candidates like Value and Value[] compete.

Input:

declare function flat<T>(args: T[] | T[][]): void;
type Value = 1 | 2;
declare const n: Value[] | Value[][];
flat(n);

tsc:

Type checks (`T` is inferred as `Value`).

tsgo:

error TS2345: Argument of type 'Value[][] | Value[]' is not assignable to parameter of type 'Value[][][] | Value[][]'.

Type checking: duplicate identifier between class declaration and expando function property assignment

GitHub ticket: #1783
Repro: Playground
Resolution: Won't fix

tsgo reports a duplicate identifier between a static class member declaration and an expando assignment on a same-named function, while tsc allows it.

Input:

declare class Logger {
    static instance(): void;
}

function Logger() { }

Logger.instance = function () { };

tsc:

No error.

tsgo:

Duplicate identifier 'instance'.

Suggested workaround from the issue discussion:

(Logger).instance = function () { };

Activity

  1. RyanCavanaugh commented on May 19, 2026

    @RyanCavanaugh
    Member

    Great report! Thanks for all the hard work investigating this. Glad to hear things are going well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions