Description
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
.