Repository navigation
Incompatible code unexpectedly compiles due to intersection with empty mapped type #19869
Description
Activity
Also interesting is if you change
takesNum:declare function takesNum(args: number): void f1(["a"], [], takesNum) // now errors of course but with the totally incorrect inferred type of Args: // Error:(20, 15) TS2345: Argument of type '(args: number) => void' is not assignable to parameter of type // '(args: Args<"a", "toString" | "toFixed" | "toExponential" | "toPrecision" | "valueOf" | "toLocaleString">) => void'.
FYI was looking through changes recently to guess what caused the behavior change and it seems almost certain to be #19745 so maybe this behavior is expected? Looking at the original bug(#19576) that was addressing I can see it'd be a very fine needle to thread to somehow both fail in my example and compile in the #19576 example.
I did discover that I can work around half of my problem by doing:
type Args<S extends string, N extends string> = Record<S, string> & Record<N, number> declare function f2<S extends string, N extends string, F extends (args: Args<S, N>) => void> (strs: S[], nums: N[], fun: F): void declare function takesNum(args: {a: number}): void declare function takesOptionalNum(args: {a?: number}): void f2(["a"], [], takesNum) // Now correctly errors because of incompatibility of "a" f2(["a"], [], takesOptionalNum) // *** Still unexpectedly compiles despite incompatibility of "a" *** f2(["a"], ["b"], takesOptionalNum) // Now correctly errors because of incompatibility of "a"
But as the comments show, the optional case still works incorrectly even after that.
Should be addressed by #19912
- addedBugA bug in TypeScriptA bug in TypeScriptFixedA PR has been merged for this issueA PR has been merged for this issue
on Nov 10, 2017 I think this is working as intended and it isn't affected by #19912. In this call
f1(["a"], [], takesNum);
we make the inference
"a"forSfrom the first argument and no inferences from the second argument (or, following #19912, a low priority inference ofneverforN). Then, from the third argument, we make the inference"a"for bothSandN. All up this means we infer"a"for both type arguments. Once we instantiate with those inferences we getf1(strs: "a"[], nums: "a"[], fun: (args: Args<"a", "a">) => void): void;
which is equivalent to
f1(strs: "a"[], nums: "a"[], fun: (args: { a: string } & { a: number }) => void): void;
which is a signature for which all of the arguments are compatible.
Reacted by Kevin Donnelly- addedWorking as IntendedThe behavior described is the intended behavior; this is not a bugThe behavior described is the intended behavior; this is not a bugand removedBugA bug in TypeScriptA bug in TypeScriptFixedA PR has been merged for this issueA PR has been merged for this issue
on Nov 10, 2017 - locked and limited conversation to collaborators
on Jun 14, 2018
TypeScript Version: Version 2.7.0-dev.20171109
Code
Expected behavior:
All calls to
f1should error becausetakesNumandtakesOptionalNumexpectato be a number but it will actually be a string.Actual behavior:
It only errors if the second argument to f1 is not empty.
Also the first case shown above:
f1(["a"], [], takesNum)actually errored as expected as recently as last week(I believe I was on 2.7.0-dev.20171031), it was only the optional case that unexpectedly compiled. I was about to open this for just the optional case when I checked the latest typescript version and noticed the first case started being affected also.