I'm running into very weird behavior when using identity functions. I'm writing a wizard system with schema (attached is a playground link with a very simplified version), and using constrained identity function to get inference.
The problem occurs in one of the properties that cannot be inferred when I use either of these:
return
keyword (rather than a single-line return wrapped with parentheses).
ORParameters<typeof myFunction>
it's inferred correctly both when declaring the argument and when I not.Both these issues are super weird to me, which means I'm either missing something very fundamental or I have found 2 rare bugs.
This reproduces in all available playground versions (tried down to 3.3.3), and also in 4.8.
Playground link with relevant code
Probably better to check the playground for code examples, but there:
TYPES DECLERATIONS:
type Schema = Record<string, unknown> // modified from original for the sake of the example, if it doesn't make sense
type StepFunction<
TSchema extends Schema = Schema,
> = (anything: unknown) => {
readonly schema: TSchema
readonly toAnswers?: (keys: keyof TSchema) => unknown
}
function step<TSchema extends Schema = Schema>(
stepVal: StepFunction<TSchema>,
): StepFunction<TSchema> {
return stepVal
}
EXAMPLES: Notice the returned object of all functions is the same! The differences are in:
return
keyword or not (?!?!)step
function or not. not that if I do Parameters<typeof myStepValue>
even when the argument is missing, it's inferred correctly (!)// WORKS: `keys` is inferred based on the `schema`
// - no argument for `step` function
// - no `return` keyword
const workingExample = step(() => ({
schema: {
attribute: 'anything',
},
toAnswers: keys => {
// RESULT: `keys` inferred successfully as `attribute`
type Test = string extends typeof keys ? never : 'true'
const test: Test = 'true'
return { test }
},
}))
// FAILS: `keys` is not inferred based on the `schema`
// - has argument for `step` function
const nonWorkingA = step(_something => ({
schema: {
attribute: 'anything',
},
toAnswers: keys => {
// RESULT: `keys` failed to inferred hence defaults to `string`
type Test = string extends typeof keys ? never : 'true'
const test: Test = 'true'
return { test }
},
}))
// FAILS: `keys` is not inferred based on the `schema`
// - has `return` keyword rather than a "single-return" return with parentheses
const nonWorkingB = step(() => {
return {
schema: {
attribute: 'anything',
},
toAnswers: keys => {
// RESULT: `keys` failed to inferred hence defaults to `string`
type Test = string extends typeof keys ? never : 'true'
const test: Test = 'true'
return { test }
},
}
})
Apparently this is a TypeScript inference issue about context-sensitive expressions (see https://github.com/microsoft/TypeScript/issues/49951 and https://github.com/microsoft/TypeScript/issues/47599).
However, I did find a temp solution - by wrapping the return value with an identity function, the edge cases work.
e.g.:
step(_something => (identityOfStep({
schema: {
attribute: 'anything',
},
toAnswers: keys => {
// RESULT: `keys` failed to inferred hence defaults to `string`
type Test = string extends typeof keys ? never : 'true'
const test: Test = 'true'
return { test }
},
})))