Skip to content

Error on types when using multiple conditional properties #58432

Closed as not planned
@Arkellys

Description

@Arkellys

🔎 Search Terms

"jsdoc conditional properties", "typescript conditonnal properties", "type conditon"

🕗 Version & Regression Information

This is the behavior in every version I tried, and I reviewed the FAQ for entries about conditional types

⏯ Playground Link

https://www.typescriptlang.org/play/?ts=5.4.5#code/FASwdgLgpgTgZgQwMZQAQGED2YAmIIjboBqCANgGLkDOaA3sKk6kgPwBcqiZtwAvsFCRYiFBmx4CRUmQAqMAK71GzJJwiKo-QeGjxkaLLnyEwAIRlUey5qgBGHLjS0Che0YYknsF8vKWoDLZ26prabiIGqGYItIEqTAic1BrgAOb8ANyCSNgpqND5ALyoABS5YHAgaZwxcQBkZUaSpr5ymqgAPuLGUuaWzgCUqI2lzd5gJH4d3eN9U5RDw0UAfE7WwBX5MFAQCjDmmJhkqCWlAG7knHZHZFAIYMPXt-dgp2uol2TZwIUQpXRUElUAAiBAggA09k4Oz2BzMt1KGiUwz4gyAA

💻 Code

interface ConditionCValFalse {
    c?: false
}

interface ConditionCValTrue {
    c: true
}

interface ConditionBValFalse {
    b?: false
}

interface ConditionBValTrue {
    b: true
}

interface Base {
    a: string
};

const test = (config: Base & (ConditionBValTrue | ConditionBValFalse) & (ConditionCValTrue | ConditionCValFalse )) => false
const returnBool = (val: boolean) : boolean =>  val;

test({ a: "a", b: returnBool(true) }) // Type 'boolean' is not assignable to type 'true'

🙁 Actual behavior

When using a second set of conditional properties, the first one throws a type error while it passes when using the condition alone.

Argument of type '{ a: string; b: boolean; }' is not assignable to parameter of type 'Base & (ConditionBValFalse | ConditionBValTrue) & (ConditionCValFalse | ConditionCValTrue)'.
  Type '{ a: string; b: boolean; }' is not assignable to type 'Base & ConditionBValTrue & ConditionCValTrue'.
    Type '{ a: string; b: boolean; }' is not assignable to type 'ConditionBValTrue'.
      Types of property 'b' are incompatible.
        Type 'boolean' is not assignable to type 'true'.(2345)

If I remove the second condition, or that I passes true or false directly, I get not error:

const test = (config: Base & (ConditionBValTrue | ConditionBValFalse)) => false
const returnBool = (val: boolean) : boolean =>  val;

test({ a: "a", b: returnBool(true) })
const test = (config: Base & (ConditionBValTrue | ConditionBValFalse) & (ConditionCValTrue | ConditionCValFalse)) => false
const returnBool = (val: boolean) : boolean =>  val;

test({ a: "a", b: true })

Somehow, when passing the c property – which is not supposed to be required – it passes:

const test = (config: Base & (ConditionBValTrue | ConditionBValFalse) & (ConditionCValTrue | ConditionCValFalse)) => false
const returnBool = (val: boolean) : boolean =>  val;

test({ a: "a", b: returnBool(true), c: true })

It also passes if I only declare the a required property:

test({ a: "a" })

So b and c are indeed optional.

🙂 Expected behavior

I expect the type checking to be the same whether I passes one or multiple conditional properties. I believe all of these should work:

// Pass
test({ a: "a" })
test({ a: "a", b: true })
test({ a: "a", c: false })
test({ a: "a", b: returnBool(true), c: returnBool(false) })
test({ a: "a", b: returnBool(true), c: false })
test({ a: "a", b: true, c: returnBool(false) })

// Currently fail
test({ a: "a", c: returnBool(true) })
test({ a: "a", b: returnBool(true) })

Additional information about the issue

For the context, I'm trying to make some properties required based on other property values.

I realize that maybe there is better way to do this in TS, but actually my problem is in JSDoc, and I believe I'm more limited on what I can do with JSDoc synthax.

/**
 * @typedef {object} ConditionCValFalse
 * @property {false} [c]
 */

/**
 * @typedef {object} ConditionCValTrue
 * @property {true} c
 */

/**
 * @typedef {object} ConditionBValFalse
 * @property {false} [b]
 */

/**
 * @typedef {object} ConditionBValTrue 
 * @property {true} b
 */

/**
 * @typedef {object} Base
 * @property {string} a
 */

/**
 * @param {Base & (ConditionBValTrue | ConditionBValFalse) & (ConditionCValTrue | ConditionCValFalse)} config
 */
const test = (config) => false;

/**
 * @param {boolean} val
 * @returns {boolean}
 */
const returnBool = (val) =>  val;

test({ a: "a", b: returnBool(true) }); // Type 'boolean' is not assignable to type 'true'

My real code is on a React component, and unlike the example above, even passing all the optional properties throws the error.

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