Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inconsistent type widening behavior with readonly object declaration in function call #26158

Closed
kube opened this issue Aug 2, 2018 · 3 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@kube
Copy link

kube commented Aug 2, 2018

TypeScript Version: 3.0.1

Search Terms

  • readonly
  • literal type
  • object prop
  • inference
  • type widening

Code

TypeScript currently behaves inconsistently when declaring a readonly object directly in a function call.

const readonlyNamedProp = <
  T extends { hello: string }
>(input: Readonly<T>) => input

const readonlyObject = <
  T extends {}
>(input: Readonly<T>) => input

Expected behavior

const a = readonlyNamedProp({ hello: 'World' })
type A = typeof a // { readonly hello: 'World' }

const b = readonlyObject({ hello: 'World' })
type B = typeof b // { readonly hello: 'World' }

Actual behavior

const a = readonlyNamedProp({ hello: 'World' })
type A = typeof a // { readonly hello: 'World' }

const b = readonlyObject({ hello: 'World' })
type B = typeof b // { readonly hello: string }

Example usage

I'm willing to use this on a Runtime Type Check library (still dirty code):

https://github.com/kube/structype

Current rework

const Animal = Type({
  kind: 'animal',
  age: Number,
  name: String
})

if (Animal.test(someObj)) {
  someObj.kind // 'animal' at runtime, but string statically
}

Workaround is to wrap kind in a function call, which adds unnecessary verbosity:

const Animal = Type({
  kind: Type('animal'),
  age: Number,
  name: String
})

Playground Link

Related Issues

#10195
#14194
#20195

@ghost ghost added the Bug A bug in TypeScript label Aug 2, 2018
@RyanCavanaugh RyanCavanaugh added this to the TypeScript 3.1 milestone Aug 8, 2018
@ahejlsberg
Copy link
Member

This is working as intended. In readonlyNamedProp, the argument is contextually typed by { hello: string } (the constraint of T) which is taken as an indication that you want to infer a literal type for the hello property (i.e. some type that is a subtype of string). However, in readonlyObject there is no constraint and therefore no contextual type for the hello property, and since we're inferring for a mutable location we widen type 'World' to string.

@ahejlsberg ahejlsberg removed their assignment Aug 9, 2018
@ahejlsberg ahejlsberg added Working as Intended The behavior described is the intended behavior; this is not a bug and removed Bug A bug in TypeScript labels Aug 9, 2018
@ahejlsberg ahejlsberg removed this from the TypeScript 3.1 milestone Aug 9, 2018
@kube
Copy link
Author

kube commented Aug 9, 2018

Ok I understand in case T extends {}.
But if T extends { [key: string]: string }, shouldn't the hello prop type be narrowed to 'World'?

const readonlyObject = <
  T extends { [key: string]: string }
>(input: Readonly<T>) => input

const b = readonlyObject({ hello: 'World' })
type B = typeof b.hello // string

Playground link

@typescript-bot
Copy link
Collaborator

This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

4 participants