Skip to content

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

Closed
@kube

Description

@kube

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

Activity

ghost added
BugA bug in TypeScript
on Aug 2, 2018
added this to the TypeScript 3.1 milestone on Aug 8, 2018
ahejlsberg

ahejlsberg commented on Aug 9, 2018

@ahejlsberg
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.

removed their assignment
on Aug 9, 2018
added
Working as IntendedThe behavior described is the intended behavior; this is not a bug
and removed
BugA bug in TypeScript
on Aug 9, 2018
removed this from the TypeScript 3.1 milestone on Aug 9, 2018
kube

kube commented on Aug 9, 2018

@kube
Author

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

typescript-bot commented on Dec 14, 2018

@typescript-bot
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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Working as IntendedThe behavior described is the intended behavior; this is not a bug

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @kube@ahejlsberg@RyanCavanaugh@typescript-bot

        Issue actions

          Inconsistent type widening behavior with readonly object declaration in function call · Issue #26158 · microsoft/TypeScript