-
Notifications
You must be signed in to change notification settings - Fork 12.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Generics: Function's template param inferred in unexpected way #20643
Comments
@RyanCavanaugh Could you provide an example of how I would use type subtraction to get the behavior I want? This is my initial guess: function visitNullableString<S extends (string - "handleNullOrUndefined")>(
value: S | null | undefined,
visitor: NullableStringVisitor<S>
): void The non-inferred type parameter solution is more obvious: function visitNullableString<S extends string>(
value: S | null | undefined,
visitor: NullableStringVisitor<NoInfer<S>>
): void |
SOLUTION!!! (hack???) type NoInfer<T> = T & {[K in keyof T]: T[K]};
function visitNullableString<S extends string>(
value: S | null | undefined,
visitor: NullableStringVisitor<NoInfer<S>>
): void Thanks to this comment for the |
Follow up on the previous hack: it's good enough to give me something functional for now, but it would be much more useful if I could prevent inferring the type of function visitNullableString<S extends string>(
value: S | null | undefined,
visitor: NoInfer<NullableStringVisitor<S>>
): void However, the hack This limitation is preventing me from overloading a single Technically, I can overload the method as desired, and it works when correct params are passed in. But when incorrect params are passed in (visitor missing a handler for one of the string values, or for null/undefined), then the compiler error messages are very misleading because it ends up inferring the type of the function visitString<S extends string>(
value: S,
visitor: StringVisitor<NoInfer<S>>
): void;
function visitString<S extends string>(
value: S | null | undefined,
// When a call to visitString is made with incompatible types for 'value' and visitor',
// the compiler seems to always default to assuming this last overloaded signature
// which can cause an error to complain about missing"handleNullOrUndefined"
// property even when the value of `value` passed in can never be null/undefined
visitor: NullableStringVisitor<NoInfer<S>>
): void; For now, the best I can reasonably do is a separate method for each combination of null/undefined. The result is helpful error messages when a visitor is not fully implemented with all necessary handlers, but the developer must explicitly choose the correct variation of Here's a full example in the Playground with separate visit methods (enable ALL compiler options). Let me know if you'd also like me to setup an example with my attempt to overload the |
Or maybe my new issue is more about function overloading rather than type param inference? |
Nevermind on my previous long comment. I've figured out that it's an issue with compile error reporting on overloaded functions. Creating a new issue... #20697 |
My final solution is to split things up so the compiler only has to deal with inferring/enforcing the type of one parameter at a time. My original goal was to have a single overloaded method. Example: function visitString<S extends string, R>(value: S, visitor: StringVisitor<S, R>): R;
function visitString<S extends string, R>(value: S | null, visitor: StringVisitorWithNull<S, R>): R; The goal being that if a potentially null value is passed in, then a corresponding appropriate visitor that can handle null must be supplied. This approach had 3 distinct issues:
In my final solution, the signature(s) of my method looks more like this: declare function visitString<S extends string>(value: S, visitor): StringVisitee<S> ;
declare function visitString<S extends string>(value: S | null): StringVisiteeWithNull<S>; And the declare class StringVisitee<S extends string> {
public with<R>(visitor: StringVisitor<S, R>): R;
}
declare class StringVisiteeWithNull<S extends string> {
public with<R>(visitor: StringVisitorWithNull<S, R>): R;
} And the complete usage now looks like: declare x: RGB;
visitString(x).with({
"red": () => { alert("RED!"); },
"green": () => { alert("GREEN!"); },
"blue": () => { alert("BLUE!"); },
}); This solves all 3 problems I had:
Example of explicit return type: declare x: RGB;
const result = visitString(x).with<number>({
"red": () => {
return 1;
},
"green": () => {
return 2;
},
"blue": () => {
// very clear compiler error that this method returns the wrong type
return "WRONG!";
},
}); I implemented a full example in the playground (must enable strictNullChecks!) |
In case anyone comes across this and is interested in this string visitor pattern I've been trying to create, I have now fully documented and tested it and put it on github: https://github.com/UselessPickles/ts-string-visitor And published it as an npm package: https://www.npmjs.com/package/ts-string-visitor |
Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed. |
TypeScript Version: 2.6.2
Code
Expected behavior:
In the call to
visitNullableString
, typeS
should be directly inferred from paramvalue
as"red" | "green" | "blue"
, and the expected type ofhandleNullOrUndefined
onvisitor
should be(value: null | undefined) => void
, becausevisitor
should be of typeNullableStringVisitor<"red" | "green" | "blue">
.Actual behavior:
Type
S
is indirectly inferred from paramvisitor
as"red" | "green" | "blue" | "handleNullOrUndefined"
, based on the names of all properties in the visitor implementation. The compiler then complains about incompatible types onhandleNullOrUndefined
, because the implementation does not accept the string literal type"handleNullOrUndefined"
.Workaround
If I explicitly provide the template param, then it works as desired:
The text was updated successfully, but these errors were encountered: