Skip to content

Proposal: A way to infer unique symbol and enum member #22118

Closed
@Jack-Works

Description

@Jack-Works

Thanks to @s-ve ,I've resolved my questions. But there is still something we can discuss.
Let's focus on Example 3 and 7.

// 3.
enum _C { A, B, C }
const c = fn(_C.A) // can not express if I want to infer this constant to have type _C.A (Now `_C`)

// 7. 
function fn<T extends Symbol>(x: T): T {return x}
const Symb = Symbol()
const g = fn(Symb) // expected to have type `unique symbol` refer to Symb but `symbol`

How to write type if you want a literal generics

// DO
const ActionCreator = <T extends string, P = any>(type: T) => (payload: P) => ({ type, payload });
const TodoAddOne = ActionCreator("todo.add");

// DONT
const ActionCreator = <T, P = any>(type: T) => (payload: P) => ({ type, payload });
const TodoAddOne = ActionCreator<"todo.add">("todo.add");

Content below is useless now, I've learnt the correct way to write types I want(See above).


Search Terms: type string literal

In some scenarios, we need to get type inference by a sure string, but not a string type.
(Most famous one is ActionCreator in Redux, but not the only one.)

Now, typescript can infer the type of (function <T>(x: T): T {return x})('hello') is the string, but if we want to get a more precise infer, it seems no way to do this.
I'm sorry for my ignorance, typescript actually can do this.
I'll show how in my following examples.

This is not a formal language feature proposal. But a demo one is enough to explain what I mean.
This how ActionCreator works now:

// Before (Don't)
const ActionCreator = <T, P = any>(type: T) => (payload: P) => ({type, payload})
const TodoAddOne = ActionCreator<'todo.add'>('todo.add')

// After(Do)
const ActionCreator = <T extends string, P = any>(type: T) => (payload: P) => ({ type, payload });
const TodoAddOne = ActionCreator("todo.add");

Now TodoAddOne has type (payload: any) => { type: "todo.add"; payload: any; }

function ActionCreator<Payload, literal Action>(type: Action){
    return (payload: Payload) => ({ type, payload })
}
const TodoAddOne = ActionCreator<{add: number}>('todo.add')

With no duplication of todo.add, we get the same type as above.
Though this is a small reduction, it goes useful when actions get greater.

How do I think this should work? (NO, Skip this section)

  1. Since who wrote the code choose to use a literal type, if Typescript cannot infer the precise type of it(not the string I want, just string type), Typescript should emit an Error.
const fn = <literal T>(a: T) => a
// 1. Direct infer (ts can do this)
const a = fn('hey') // has type 'hey'

// 2. Direct infer (number) (ts can do this)
const b = fn(2) // has type 2

// 3. Direct infer (enum) (No, ts cannot do this)
enum _C { A, B, C }
const c = fn(_C.A) // has type _C.A

// 4. Infer from constant (Rejected proposal)
const _d_name = 'nyaa'
const d = fn(_d_name) // has type 'nyaa'

// 5. Infer from **simple** string operation (Rejected proposal)
const _e_name = 'prefix~'
const e = fn(_e_name + 'hello') // has type 'prefix~hello'

// 6. Infer from another file (if possible) (Rejected proposal)
import { OH_MY_LITTLE_PONY } from './constant'
// export const OH_MY_LITTLE_PONY = 'MyLittlePony' in 'constant.ts'
const f = fn(OH_MY_LITTLE_PONY) // has type 'MyLittlePony'

// 7. Infer from Symbol() (No, ts cannot do this, it has type `symbol` now)
const Symb = Symbol()
const g = fn(Symb) // has type `unique Symbol`

// 8. Report error if compiler cannot infer the type (Rejected proposal)
const _h: any = 'hello'
const h = fn(_h)
//        ~~~~~~
// `any` is incompatiable with type `literal T`.
// You must provide a presice type for `literal T`

// 10. Maybe a way to bypass the error? (Useless now since 8 is rejected.)
const q: any = 0
fn(q!) // has type any

// 11. Not in generics (// Well, this case works when using 'const' but not 'let', that's reasonable.)
const n: literal string = 'okay' // has type 'okay'

Metadata

Metadata

Assignees

No one assigned

    Labels

    Design LimitationConstraints of the existing architecture prevent this from being fixed

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions