Skip to content

Incorrect object properties type inference with aliased conditional expressions #45624

Closed
@ruizb

Description

@ruizb

Bug Report

🔎 Search Terms

type inference, inference, aliased condition, aliased conditional expression

🕗 Version & Regression Information

It started happening when the aliased conditional expressions were introduced in the following pull-request:

This issue is still present as of v4.4.2.

I was unable to test this on prior versions because aliased conditional expressions didn't exist previously.

⏯ Playground Link

Playground link with relevant code.

💻 Code

function hasOwnProperty<X extends {}, Y extends PropertyKey>
  (obj: X, prop: Y): obj is X & Readonly<Record<Y, unknown>> {
  return obj.hasOwnProperty(prop)
}

const isObject = (val: unknown): val is object => val !== null && typeof val === 'object'
const isString = (val: unknown): val is string => typeof val === 'string'

declare const x: unknown
const isAnObject = isObject(x)
const hasValidName = isAnObject && hasOwnProperty(x, 'name') && isString(x.name)

if (hasValidName) {
  const res0 = x      // object & Readonly<Record<"name", unknown>>
  const res1 = x.name // string
}

If it can help, I generated a .types file as well.

See the .types file contents.
=== tests/cases/compiler/_baguette.ts ===
function hasOwnProperty<X extends {}, Y extends PropertyKey>
>hasOwnProperty : <X extends {}, Y extends PropertyKey>(obj: X, prop: Y) => obj is X & Readonly<Record<Y, unknown>>

  (obj: X, prop: Y): obj is X & Readonly<Record<Y, unknown>> {
>obj : X
>prop : Y

  return obj.hasOwnProperty(prop)
>obj.hasOwnProperty(prop) : boolean
>obj.hasOwnProperty : (v: PropertyKey) => boolean
>obj : X
>hasOwnProperty : (v: PropertyKey) => boolean
>prop : PropertyKey
}

const isObject = (val: unknown): val is object => val !== null && typeof val === 'object'
>isObject : (val: unknown) => val is object
>(val: unknown): val is object => val !== null && typeof val === 'object' : (val: unknown) => val is object
>val : unknown
>val !== null && typeof val === 'object' : boolean
>val !== null : boolean
>val : unknown
>null : null
>typeof val === 'object' : boolean
>typeof val : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>val : unknown
>'object' : "object"

const isString = (val: unknown): val is string => typeof val === 'string'
>isString : (val: unknown) => val is string
>(val: unknown): val is string => typeof val === 'string' : (val: unknown) => val is string
>val : unknown
>typeof val === 'string' : boolean
>typeof val : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>val : unknown
>'string' : "string"

declare const x: unknown
>x : unknown

const isAnObject = isObject(x)
>isAnObject : boolean
>isObject(x) : boolean
>isObject : (val: unknown) => val is object
>x : unknown

const hasValidName = isAnObject && hasOwnProperty(x, 'name') && isString(x.name)
>hasValidName : boolean
>isAnObject && hasOwnProperty(x, 'name') && isString(x.name) : boolean
>isAnObject && hasOwnProperty(x, 'name') : boolean
>isAnObject : boolean
>hasOwnProperty(x, 'name') : boolean
>hasOwnProperty : <X extends {}, Y extends PropertyKey>(obj: X, prop: Y) => obj is X & Readonly<Record<Y, unknown>>
>x : object
>'name' : "name"
>isString(x.name) : boolean
>isString : (val: unknown) => val is string
>x.name : unknown
>x : object & Readonly<Record<"name", unknown>>
>name : unknown

if (hasValidName) {
>hasValidName : boolean

  const res0 = x
>res0 : object & Readonly<Record<"name", unknown>>
>x : object & Readonly<Record<"name", unknown>>

  const res1 = x.name
>res1 : string
>x.name : string
>x : object & Readonly<Record<"name", unknown>>
>name : string
}

🙁 Actual behavior

The type of the name property of x is inferred as unknown (cf. res0).
However, when accessing the property with x.name (cf. res1), its type is correctly inferred as string.

🙂 Expected behavior

The name property should be inferred as string when checking the properties of x.

For example, as a developer, when typing x., I'm expecting to see name: string in the "auto-completion" list, instead of name: unknown.

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