microsoft / TypeScript

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

Omit private symbols from type #60504

Open trusktr opened 1 day ago

trusktr commented 1 day ago

πŸ” Search Terms

Continuation of

βœ… Viability Checklist

⭐ Suggestion

Please remove private symbols from types. There's no need to check symbols the same way as private properties or #private fields, because unlike private properties or #private fields symbols can only ever be accessed using a reference to the symbol.

So, for example, if an object is exported from a module (or returned from a function) the type does not need to be included outside of the module (or outside of the function) if the symbol is not in scope, because the user of the object will never be able to access the symbol key as they do not have a reference to the symbol.

This will prevent unintuitive errors like Return type of exported function has or is using private name 'someSymbol'.

In fact, a symbol is not really private at all. It is a public key, so long as you have the reference. For this reason, it needs special and different treatment to make their usage easier.

πŸ“ƒ Motivating Example

πŸ’» Use Cases

  1. What do you want to use this for?
    • Use symbols as semi-"private" public keys without type errors.
  2. What shortcomings exist with current approaches?
    • Unnecessary errors that don't really help.
  3. What workarounds are you using in the meantime?
    • Write return types explicitly which can be more cumbersome,
    • or type the symbol as any but then type checking within code is lost.
jcalz commented 1 day ago

I don't get it. The suggestion in the other issue had the problem where if the internal state of the exposed thing cares about that symbol-keyed property, then merely omitting it produces the wrong type. I don't see a code example here, but how should TS deal with

const f = (() => {
    const sym = Symbol();
    let id = 0;
    function make(name: string) {
        return {
            name,
            [sym]: id++
        }
    }
    type Item = ReturnType<typeof make>
    return {
        make,
        take(item: Item) {
            console.log(item.name);
            console.log(item[sym].toFixed(0));
        }
    }
})()
/* const f: {
    make: (name: string) => {
        name: string;
        [sym]: number;
    };
    take(item: {
        name: string;
        [sym]: number;
    }): void;
} */
f.take(f.make("okay")); // okay, 0
f.take(f.make("yeah")); // yeah, 1
f.take({ name: "oops" }); // oops, πŸ’₯

Playground link to code

Are you suggesting that the return type of f.make() and the parameter type of f.take() should be {name: string} and therefore f.take({name: "oops"}) should not give the TS error it gives now? This looks the same as private and #private to me. They have the effect of making the type effectively nominal, but that's a good thing, right?