TypeScript icon indicating copy to clipboard operation
TypeScript copied to clipboard

Unexpected behavior (possible bug)

Open artem-malko opened this issue 5 years ago • 3 comments

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

artem-malko avatar Aug 10 '20 06:08 artem-malko

@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 avatar Aug 11 '20 00:08 DanielRosenwasser

@DanielRosenwasser thanks for reply. Yes, you understood me correctly)

There are some questions:

  1. Why typescript can not infer type for w from Params<T>? In Params it has static type — string.
  2. What is the difference between object and separate arguments? If there is a page in docs — just post a link)

artem-malko avatar Aug 11 '20 01:08 artem-malko

: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: -

  • Object is of type 'unknown'.

typescript-bot avatar Apr 13 '22 23:04 typescript-bot

This was fixed in 4.7. Closing the issue.

ahejlsberg avatar Sep 27 '23 15:09 ahejlsberg