TypeScript Version: 3.8.0-dev.20191025
Search Terms:
unknown noimplicitany
noimplicitany inline function
Code
interface SyncProjector<TSource> {
(source: TSource): any;
}
function wth<T>(func: SyncProjector<T>) {
return 0;
}
wth(dat=>dat);
Expected behavior:
The inline dat=>dat function should complain about implicit any
Actual behavior:
It doesn't complain even in strict mode. In fact, I can't figure out any way at all to make it complain about this while constraining func to be some kind of function.
Playground Link:
Already boiled it down to 7 lines above
Related Issues:
None
It鈥檚 actually not implicit any: dat gets inferred as explicit any due to contextual typing. That in turn causes T to be inferred as any.
Why any? T's type (and hence, TSource's type) isn't specified - vscode and tsplayground say it's "unknown".
Maybe my example threw you off, since TNext and TSource were in a weird order? I just removed TNext so it's a little simpler and clearer.
Am I just approaching the problem wrong? I just want to say "this generic function should have a parameter that needs to be a function with a parameter". The real thing I'm doing is more complex, but the problem I'm having comes down to this particular behavior.
Hmm... in the new rewritten example, dat is inferred as unknown, not any.

I believe this happens because there is no inference site for T, so it defaults to the constraint. The default constraint for a generic is unknown (used to be {}). You can see this if you give a default type for T, e.g. T = number, then the parameter type defaults to number.
What you'd really need to make this work the way you want, I think, is an "explicit implicit any", then you could go T = impany and dat => dat would then trigger the error.
There's no implicit any here; dat is of type unknown.
Right, I agree that under current typing rules, dat is unambiguously unknown. However, it is unfortunate that normal inference when applied to dat => dat produces an implicit-any error, while there is no way to get the same error in the presence of a generic because of contextual typing. We're asking to infer T from the parameter of a function that was passed in, but under normal inference rules with --noImplicitAny enabled, that parameter doesn't actually have a type. It's only thanks to the feedback loop between generics and contextual typing that it gains a type: unknown. Which is IMO, confusing.
Thank you both for your help.
@fatcerberus yes, you articulated it way better than I could - I understand now why it's behaving the way it does, but the inconsistency between how it behaves when not generic is confusing.
I first thought the "impany" thing you suggested was some hidden feature I didn't know about :D
FYI I tried jimmying it up with what's already available like this:
type knownornever<TKnown, TThen> = unknown extends TKnown
? // must be unknown or any
TKnown extends TKnown & string
? // only any behaves this way, so allow it
TThen
: // must be unknown, so disallow it
never
: // is neither unknown nor any
TThen;
interface SyncProjector<TSource> {
(source: TSource): any;
}
function wth<T>(func: knownornever<T, SyncProjector<T>>) {
return 0;
}
wth((dat) => dat); // error \o/ yay
wth((dat: number) => dat); // works \o/ yay
wth((dat: any) => dat); // works \o/ yay
It "works" (behaves like a non-generic would), but it's a little hokey and rigid. If the "impany" thing is possible, that would make this a lot simpler.
@captaincaius For posterity, and for anyone else who happens upon this issue in the future, here's a rough play-by-play of what happens:
wth is called, passing the lambda expression (arrow function) dat => dat.wth<T> is generic, and we need an inference for T. TS attempts to infer T from the parameter type of func. This fails, as dat has no type annotation. There are no other inference sites.T, the compiler defaults it.T, so its upper bound is chosen--in this case unknown.wth<T> is now fully instantiated as wth<unknown>, which is the final call target.dat has no type annotation, so contextual typing kicks in to try to infer it.unknown from type parameter inference is fed back into contextual typing and becomes the type of dat (in more prosaic terms, the final call target is not generic; contextual typing infers func based on a concrete target type of (source: unknown) => any).Thanks for the awesome detailed run through!
Extremely good summary @fatcerberus 馃
This issue has been marked as 'Question' and has seen no recent activity. It has been automatically closed for house-keeping purposes. If you're still waiting on a response, questions are usually better suited to stackoverflow.
Most helpful comment
@captaincaius For posterity, and for anyone else who happens upon this issue in the future, here's a rough play-by-play of what happens:
wthis called, passing the lambda expression (arrow function)dat => dat.wth<T>is generic, and we need an inference forT. TS attempts to inferTfrom the parameter type offunc. This fails, asdathas no type annotation. There are no other inference sites.T, the compiler defaults it.T, so its upper bound is chosen--in this caseunknown.wth<T>is now fully instantiated aswth<unknown>, which is the final call target.dathas no type annotation, so contextual typing kicks in to try to infer it.unknownfrom type parameter inference is fed back into contextual typing and becomes the type ofdat(in more prosaic terms, the final call target is not generic; contextual typing infersfuncbased on a concrete target type of(source: unknown) => any).