Skip to content

"Conditionally optional" type works for concrete parameter but not genericΒ #63007

@ianroberts

Description

@ianroberts

πŸ”Ž Search Terms

conditionally optional, conditional type generics

πŸ•— 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/?#code/PTAEBUAsEsGdTqAhqW0C2AHANtAZtAKYAmohAHkltoaAPZ7KrQAuArki9HQHYJ8oASoSQBjFqFF0svQjwktInAFAhQxODiQBPeNB546AJ3SduAgEZ02ElLlgSG6zilaF08Jy22Za4AHSgoACSEjyEJPAsdKpgFrRIFjSg0eqELIQm+rSKtJBspjwAtEYixInJAG5I2Gy0TiKikAgZ6AA0qHQpkLRSMuHysSlIANaE8CiYRnSYqL6i+Nr6AOagAO4wTaBTM5ne9Iy5ziyurSk+OV0ULEZiLG1DSDyk+N2IO7OIPHQSO5XQxBI3TkwLSeCQbGwEkQqWut3EoIARNVaoREUMPnttIFQABlLrBdZPBRXAzGUQ5JTQw49UDLOSZaCic6+CCgABUSlg7KYyJqdXRakxRn2uT4Rz5qIACtNMIiEJ5MFxeDUOhYbAhGOB1HRxhzviweUpKgkhpKBdtZVjgeLaebCDKZvLEKUAI5saClYj+ZTeVkAUUo1EIAB5wAA+UAAXlAAG9lEEgoY6AAuVA3FYAbmUAF9QAAyUAACm11zkxHgsdApSQxF42G0oBRdTTbB4I2+az4eYA-HGm-yHbKe2mxtonNq82mq82hzNR4Rx1rQDmAJTZoYACUytEQKD9lM461oHboa1AXKYs8tuxF2mU+gyRnBFNAm6QsAAaoO4wnE7O0wcIws1zZQtx3BVkANHojBZQ8JDrPUDV9C5QAAOTob9UWjX9E1AA0sJbdNgJ4ZZsxzMC1GCRhCX0BxiWgThaEDKgcFoNZWGaI4pB4URSgyN8P0I2hMCQW50HSTIbQHaVZSGPdsGwM8gXVMIfmrQh3U9EhlB4hwyHIABGJA0xY4MQ3fL9B0jGMq2TNNER6RS6HlHNsz0iQKEMixTKDNiLKE6ycLsuhU1ARzCGcxEOlnR1MAc2dXOzKiaP4ej5EYgSzLY9ZONBHi+Mk9DMJ-UTxMk2CxRkuo4qGdk4B5N0PS9XTeH0igACZfNYmgQww4SbP7ezwuWULiAsbQ0Riwc4ocgjBySyiwAAIQ1V5CRuRtUjok4MqY0BsuSDjFCYKw20BUh6XCYDmQPNkqvAXFJGwJAMFgIYjgPCZSn4PpRK4JJCA6QgTVtaxlmaNYT07UBDFgwECB4NxuiPbV0DYfTjVNNR7XlYVvB9PA23EcxQFMMZDtDUtyAyZ54Esgai2GoCVlXbrzIjXDE34tgjD4EKujc0CgA

πŸ’» Code

// This is a simplified example of a situation in a React component that
// displays information about a list of data items of type T.  It needs to
// be able to determine the human-readable value of each item, so the component
// takes a prop specifying which property of the data item type to extract,
// and if this prop is not provided then the default is to extract the "value"
// property.  So I want to enforce that if the generic type T *has* a "value"
// property then the "valueProp" is optional, but if T does *not* have a
// "value" property then the "valueProp" is required.
type Example<T> = {
    foo: string;
} & (T extends { readonly value: unknown } ? { valueProp?: keyof T } : { valueProp: keyof T });

// Here is a type that we know has a value property
interface HasValue {
    value: string;
}

// Here is another type that does not
type NoValue = {
    notValue: string;
}

// If I instantiate Example with the concrete HasValue parameter then valueProp
// is allowed but not required
const ex1a: Example<HasValue> = { foo: "hello" };
const ex1b: Example<HasValue> = { foo: "hello", valueProp: "value" };
// If I instantiate Example with the concrete NoValue parameter then valueProp
// *is* required
const ex2: Example<NoValue> = { foo: "goodbye", valueProp: "notValue" };

// But if I try to instantiate Example with a bounded generic type T then TS claims
// the types are incompatible, even though we know for definite that T must have a
// "value" property.
function makeExample<T extends HasValue>(foo: string): Example<T> {
    return { foo }; // TS2322
}

πŸ™ Actual behavior

Type '{ foo: string; }' is not assignable to type 'Example<T>'.
  Type '{ foo: string; }' is not assignable to type 'T extends { readonly value: unknown; } ? { valueProp?: keyof T | undefined; } : { valueProp: keyof T; }'.(2322)

πŸ™‚ Expected behavior

The generic type variable T is constrained as T extends HasValue, which in turn extends { readonly value: unknown; }, so this should resolve to the { valueProp?: keyof T | undefined; } optional branch in exactly the same way as it does when the expression is typed as the concrete Example<HasValue>.

Additional information about the issue

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions