TypeScript
TypeScript copied to clipboard
Unexpected behavior (possible bug)
TypeScript Version: 3.9.2
Search Terms:
Type inference
Expected behavior:
Type of argument "p" in condition is inferred correctly in const a and const b.
Actual behavior:
Type of argument p in condition is inferred correctly in const a but not in const b.
Its is strange for me, that just adding of argument w in const b will broke type inference, cause w is not involved in type inference (just look on Params, T does not depend on s in Params<T>).
But all things work, if I set type for w.
So, I'd like to know, is it a bug or it is expected behavior.
Related Issues:
Code
type Params<T> = {
selector: (s: string) => T | undefined;
condition: (selectorResult: T | undefined) => boolean;
};
export function superSelector<T>(p: Params<T>) {
const selectorResult = p.selector('qwe');
return selectorResult;
}
const a = superSelector({
selector: () => 'qwe',
condition: (p) => !!(p && p.length),
})
const b = superSelector({
selector: (w) => 'qwe',
condition: (p) => !!(p && p.length),
})
Output
export function superSelector(p) {
const selectorResult = p.selector('qwe');
return selectorResult;
}
const a = superSelector({
selector: () => 'qwe',
condition: (p) => !!(p && p.length),
});
const b = superSelector({
selector: (w) => 'qwe',
condition: (p) => !!(p && p.length),
});
Compiler Options
{
"compilerOptions": {
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictPropertyInitialization": true,
"strictBindCallApply": true,
"noImplicitThis": true,
"noImplicitReturns": true,
"alwaysStrict": true,
"esModuleInterop": true,
"declaration": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"moduleResolution": 2,
"target": "ES2017",
"jsx": "React",
"module": "ESNext"
}
}
Playground Link: Provided
@ahejlsberg correct me if I'm wrong.
The idea here is that we need to get the contextual type for w in the second example. That makes us undergo call resolution to figure out the parameter type, which forces us to infer type arguments for T. In turn, T is considered "fixed" - no further inferences are made for T.
But by this point, we haven't made any inferences for T, so we infer unknown instead. When selectorResult later asks for its type, T will have been replaced with unknown.
One thing that you could do to "fix" this is to switch from using an object to using 2 separate parameters.
export declare function superSelector<T>(
selector: (s: string) => T | undefined,
condition: (selectorResult: T | undefined) => boolean,
): T | undefined;
const b = superSelector(
(w) => 'qwe',
(p) => !!(p && p.length),
);
@DanielRosenwasser thanks for reply. Yes, you understood me correctly)
There are some questions:
- Why typescript can not infer type for
wfromParams<T>? InParamsit has static type — string. - What is the difference between object and separate arguments? If there is a page in docs — just post a link)
:wave: Hi, I'm the Repro bot. I can help narrow down and track compiler bugs across releases! This comment reflects the current state of the repro in the issue body running against the nightly TypeScript.
Issue body code block by @artem-malko
:+1: Compiled
Historical Information
| Version | Reproduction Outputs |
|---|---|
| 4.2.2, 4.3.2, 4.4.2, 4.5.2, 4.6.2 |
:x: Failed: -
|
This was fixed in 4.7. Closing the issue.