Typescript: NoImplicitAny ignores inline functions that are type-constrained

Created on 26 Oct 2019  路  11Comments  路  Source: microsoft/TypeScript


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

Question

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:

  1. wth is called, passing the lambda expression (arrow function) dat => dat.
  2. 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.
  3. In the absence of an inference for T, the compiler defaults it.
  4. No default is declared for T, so its upper bound is chosen--in this case unknown.
  5. wth<T> is now fully instantiated as wth<unknown>, which is the final call target.
  6. dat has no type annotation, so contextual typing kicks in to try to infer it.
  7. The 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).
  8. As a result, no implicit-any error is produced.

All 11 comments

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.

image

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:

  1. wth is called, passing the lambda expression (arrow function) dat => dat.
  2. 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.
  3. In the absence of an inference for T, the compiler defaults it.
  4. No default is declared for T, so its upper bound is chosen--in this case unknown.
  5. wth<T> is now fully instantiated as wth<unknown>, which is the final call target.
  6. dat has no type annotation, so contextual typing kicks in to try to infer it.
  7. The 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).
  8. As a result, no implicit-any error is produced.

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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

uber5001 picture uber5001  路  3Comments

jbondc picture jbondc  路  3Comments

Roam-Cooper picture Roam-Cooper  路  3Comments

kyasbal-1994 picture kyasbal-1994  路  3Comments

bgrieder picture bgrieder  路  3Comments