Typescript: Case that infering type for parameters works for 3.3.0 but error in 3.4.0

Created on 10 Apr 2019  路  9Comments  路  Source: microsoft/TypeScript


TypeScript Version: 3.4.0-dev.201xxxxx


Search Terms:

3.4.0 regression
parameter type infer

Code

// with compilerOptions.noImplicitAny=true in tsconfig.json
export function test<S>(options: { partial: Partial<S> | undefined }) {
  const fn: any = function (state = options.partial, action_: any) {
    return state
  }
  return fn
}

The related tsconfig.json

{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "dom.iterable", "esnext"],
    "noImplicitAny": true,
    "noImplicitReturns": true,
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "preserve"
  },
  "include": ["src"]
}

Expected behavior:
No error. It's worth noting that it works in 3.3.4.

Actual behavior:

With the tsc command line, it outputs the following errors:

error TS7006: Parameter 'state' implicitly has an 'any' type.

25   const fn: any = function(state = options.partial, action_: any) {

Playground Link:

Related Issues:


The code works in 3.3.4 with no errors, but after upgrading to 3.4.0, the tsc complains that the parameter has 'any' type when the __LHS__ fn is of any type. If I replace fn: any = with fn =, it works for 3.4.0.

Bug Fix Available

Most helpful comment

@bdpartridge @christopherthielen The issues you point out above are now fixed in #36476.

All 9 comments

Simpler repro

// @noImplicitAny

// Error
const k1: any = (m = 3) => m;
// No error
const k2 = (m = 3) => m;

@ahejlsberg Andrew and I were discussing this on teams and I disagree with part of the logic in https://github.com/Microsoft/TypeScript/issues/28816#issuecomment-457408663

However, for the function expression passed as an argument to id we do have a contextual type, namely T. Since T has no constraint we should end up inferring an implicit any type for the parameter, but this isn't quite working correctly.

I don't see why this desirable at all. Unless there are call/construct signatures on the contextual type, this can only create new implicit anys where none were needed. This creates breaks in pretty basic examples (see above comments). What's the motivating example for taking an outer any contextual type (the one typing the function expression, not its parameter) and using that to add implicit anys to initialized parameters?

Yes, after I upgraded the typescript package to 3.4, suddenly this error showed up and this should not be an issue before 3.4.

@RyanCavanaugh Regarding this example:

const k1: any = (m = 3) => m;  // m: implicit any
const k2 = (m = 3) => m;  // m: number

I would absolutely argue that it is correct for the inferred type of m in the first example to be an implicit any. We know from the contextual type that anything more specific we infer for m will be disregarded, and we have no idea what type of arguments will actually be passed for m (and keep in mind that the default value only kicks in when we're passed an undefined). In the second example it is fine to infer from the initializer because the inferred type will survive and constrain arguments that can be passed for m.

Now, I think where this argument changes is when the contextual type is a generic type with a constraint. Just because the constraint says any doesn't mean that we can't infer a more specific type. So we may have some work to do there.

Possibly related to this, after upgrading from _3.3.1_ to _3.4.5_, I'm also getting implicit any errors for default parameters in a very specific case (see Case 3):

declare function memoize<F extends Function>(func: F): F;

// Case 1: No error
function add(x: number, y = 0): number {
    return x + y;
}
const memoizedAdd = memoize(add);

// Case 2: No error
const add2 = (x: number, y = 0): number => x + y;
const memoizedAdd2 = memoize(add2);

// Case 3: Error - Parameter 'y' implicitly has an 'any' type
const memoizedAdd3 = memoize((x: number, y = 0): number => x + y);

Passing an arrow function expression directly to memoize (in Case 3) results in an implicit any error for default parameter y. For the other two cases, the type of y is correctly inferred as number.

If this is unrelated to the original issue, I'd be happy to move this to a separate one.

@bdpartridge That's exactly the issue I allude to here in my comment:

Now, I think where this argument changes is when the contextual type is a generic type with a constraint. Just because the constraint says any doesn't mean that we can't infer a more specific type. So we may have some work to do there.

That's what we need to fix.

@ahejlsberg That makes sense. Thanks for explaining.

here's another surprising case:
// no error const fn1: (x: number) => number = (x = 1) => x //error const fn2: <T>(x: number) => number = <T>(x = 1) => x

@bdpartridge @christopherthielen The issues you point out above are now fixed in #36476.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Zlatkovsky picture Zlatkovsky  路  3Comments

uber5001 picture uber5001  路  3Comments

Antony-Jones picture Antony-Jones  路  3Comments

wmaurer picture wmaurer  路  3Comments

Roam-Cooper picture Roam-Cooper  路  3Comments