TypeScript: T implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer

TypeScript Version: 3.7.3

Code

type Schema = {
    [key: string]: Number | String | Boolean | (() => Model<any>)
}

type Model<T extends Schema> = {

}

function model<T extends Schema>(type: Schema): Model<T> {
    return undefined as any
}

const User = model({
    id: Number,
    name: String,
    photo: () => Photo
})

const Photo = model({
    id: Number,
    filename: String,
    user: () => User
})

Expected behavior:

No errors. I need this technic to implement automatically inferred circular types. I already have a class-based implementation (you can see example here) and I basically want same with objects of course without manually defining its type. If it’s not a bug, any advances on how to implement it keeping type inferred are highly appreciated.

Actual behavior:

Gives following error:

'User' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.(7022)

Playground Link: http://www.typescriptlang.org/play/#code/C4TwDgpgBAygxgCwgWwIZQLxQN4FgBQURUA2gNYQgBcUAzsAE4CWAdgOYC6NAcgK7IAjCAygAfWI1ZsxUAEIB7eQBsIqFjIAUGgJSYAfFACy8gCYQlAHjUg92ggF8CBUJCOnzFgCpQIAD2AQLCa0sIgoqAZYePgOTvgAZrwscMBM8urI7pbefgFBIfBIaHoaLhA0heHaNMZm2QbRxFAMEMC8DOpJZvGsECZQqCHWsTH4cOn0UACqtMKYUJl1Go3ETCY8-EIMADQETSyoyOUSzOy7hMRgCPLA8jQ6+lAACte3Dnaj4yyTLzfy84tzMs9qt1lA+IJhOcmj0VAcjhVJGcQUReLMGPddBgDDNhO8CEA

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 37
  • Comments: 25 (4 by maintainers)

Most upvoted comments

Having similar issue with very simple example

// ----------
// ALL OK here, myObject1 has all types detected
const myObject1 = {
    aaa: 50,
    bbb: () => {
        return myObject1.aaa;
    }
};

// ----------
// myObject2 has compilation error: (With strict mode ON)
// T implicitly has type 'any' because it does not have a type annotation
//     and is referenced directly or indirectly in its own initializer
const someFunc = async (input: () => number): Promise<string> => {
    // ...
};

const myObject2 = {
    aaa: 50,
    bbb: someFunc(() => {
        return myObject2.aaa;
    })
};

Would really want to know if this is temporary limitation or rather impossible to solve limitation?

(FYI - ignore that input in someFunc is never called. It can be called in async context. This example is simplified to show the problem)

Tested with TS 3.8.3

Playground link: https://www.typescriptlang.org/play/#code/PTAEFpK6FgCgSgIIBkWgPIGlQAsCmATvgDSgC2AnhgEYBW+AxgC4CMeAhgM6gcA2fUM0oAHfDwAm+ZkxkT4jAPYA7LswrV6s9gF5QAb3ihjvDhwBcoAKwAGEkZM0nlgBQBKUDoB8BhyZPEzACuhMoatAwsrAB0ZhwA3H6gAL7wyYlw8IjQOeBZYFQRsgBMnDxK5CIAlnwczFUqoESEioSuAOpVzLigaoRVLBSKUpgAcm75oAAqoFWVfANdfJRlQqL4oADkHMqUm6A0TBxBXBtdoBKK4qDKiuq4HABuGxxrYrzKt8x1DcqT-jsJLMeMQAGZEfDKRj4IESKrEFjLUCtWbKOEI5hIqphLo8RQAdxxyi6VX4VQAXkQFCo1L1FOR8AAxIJQzygFzYkRBZiuDzeG5BciHQhuSx9bEAc08PkMcH8gRCYQARDJaUqMul4NTVOpCloWKU9LKAWZLLZ7HLHM46QzmVCXO5pb5Lf5jArQuF9cxirEzBl-MkJnBNXAgA

EDIT 1: In our case someFunc is async. Sync can’t work, as object is not defined property by property, but all at once. That explains why this is an error. Problem is that it depends how people use it. In sync context it is error for obvious reasons, in async (when at least one tick is passed) it would work fine.

EDIT 2: Whoever is stuck with this, for us alternative approach worked OK: This behaves pretty much same as object definition, but class is created property by property.

// ----------
// All types are correctly detected
const myObject1 = class {
    public static aaa = 50;
    public static bbb = someFunc(() => {
        return myObject1.aaa;
    });
};

Simplified example:

async function getAString(ignore?: string): Promise<string> {
  return "ok";
}

async function example() {
  let a: string | undefined = "a";
  for (let i = 0; i < 1; i++) {
    const b = await getAString(a); // ERROR
    a = b;
  }
}

Note:

  • Error does not happen if the lines are not wrapped in a loop (even though it only loops once)
  • Error does not happen if | undefined is removed on type of a
  • Error does not happen if a = b; is removed or inlined to a = await getAString(a);
  • Error does not happen if getAString is not async

Playground Link

I originally ran into this problem trying to use aws-sdk paginated service methods, which often have a NextToken property taking the place of a in my contrived example. The above was just as simplified as I could make it. The actual workaround I used was to declare my variable outside the loop as any.

I am getting this in an even simpler case:

const [a, b = a] = [42]

You can try it out:

'a' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.

However, a should clearly be of type number, based on the way Javascript destructuring works.

Here’s another example with firebase auth and TS 4.2.3.

const admin = require('firebase-admin');

async function test() {
  const auth = admin.auth();
  // The following 2 assignments work fine
  const result1 = await auth.listUsers(/** maxResult= */ 1000);
  const result2 = await auth.listUsers(/** maxResult= */ 1000, /** pageToken= */ undefined);
  /** @type {string | undefined} */
  let pageToken = 'token';
  while(true) {
    const result3 = await auth.listUsers(/** maxResult= */ 1000, pageToken);
    // If you comment the following line TS does not complain (but the code obviously won't work)
    pageToken = result3.pageToken;
    if (pageToken === undefined) {
      break;
    }
  }
}

We are using the same pattern with the gmail api, the only difference there is how the page token is specified. Instead of being it’s own argument the gmail api received the pageToken in an options param, as in:

  /** @type {string | undefined} */
  let pageToken;
  while (true) {
    const res = await gmail.users.messages.list({ ...baseRequest, pageToken });
    pageToken = res.data.nextPageToken || undefined;
    if (pageToken === undefined) {
      break;
    }
  }

Please don’t interpret any of this as impatience or offense; I’m not sure why this happens either and also find it odd!

What I’m trying to say is that circularity errors on circularities are not per se defects, and I’m not going to reallocate finite team resources away from fixing defects or implementing features toward addressing non-defects.

Could you help me find out why it behaves differently with/without await?

The investigation itself here is the resource spend that I’m trying to avoid. Figuring out why a thing happens is usually 80% of fixing it.

Another example:

type Foo = { x: number };

async function bar(arg: Foo): Promise<void> {
  let foo: Foo | undefined = arg;

  while (foo) {
    const x = foo.x; // ERROR 7022
    foo = await makeFoo(x);
  }
}

function makeFoo(x: number): Foo | undefined {
  return Math.random() ? { x } : undefined;
}

Playground

Small non-circular example. Error shows up on the line that says “error here”

Removing the line 2 down (the property = records.property, then there is no more error.


export class Result {
    property?: string
}


export class MyObject {
    async get(property: string): Promise<Result> {
        return new Result();
    }
}

async function failingTest(queue: MyObject): Promise<void> {
    let property: string | null = null;
    let keepGoing = true;
    while (keepGoing) {
        if (!property) {
            property = "start";
        }
        const records = await queue.get(property); //ERROR HERE
        if (records.property) {
            property = records.property; //NO ERROR IF THIS LINE REMOVED
        }
        keepGoing = true;
    }
}

also if I break out the await onto 2 lines, the error goes away:

...
        const promise = queue.get(property);
        const records = await promise;
...

I have the same issue with await keyword:

async function main() {
  const exec = (_: () => void): string => ''
  const fn = (_: number) => {}
  const y = await exec(() => fn(y))
  console.log(y)
}

Without await keyword I get

Argument of type ‘string’ is not assignable to parameter of type ‘number’.

With await keyword I get

‘y’ implicitly has type ‘any’ because it does not have a type annotation and is referenced directly or indirectly in its own initializer.

This also happens to me when I try to use the aws-sdk

async function getAllParametersByPath(ssm: AWS.SSM, path: string) {
    let input: AWS.SSM.GetParametersByPathRequest | undefined = {
        Path: path,
    }

    const params: AWS.SSM.Parameter[] = []
    while (input) {
        const { Parameters = [], NextToken } = await ssm.getParametersByPath(input).promise()
        params.push(...Parameters)

        input = NextToken ? { ...input, NextToken } : undefined
    }
    return params
}

It infers Parameters: AWS.ParameterList, but gives me the same error on NextToken

'NextToken' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.ts(7022)

EDIT: I started out with the below example and that works:

import AWS from 'aws-sdk'

async function handler() {
    const ssm = new AWS.SSM()
    const { Parameters = [], NextToken } = await ssm
        .getParametersByPath({ Path: '/prefix/' })
        .promise()
}

Apparently, the circularity is in input? But it is detected only on NextToken (and not on Parameters)