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

Created on 6 Dec 2019  路  11Comments  路  Source: microsoft/TypeScript

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

Design Limitation

Most helpful comment

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;
    });
};

All 11 comments

Hello,
I have another small snippet that shows a similar error:

class A<T>
{
    public readonly b = new B( this );
}
class B
{
    constructor( a: A<any> )
    {

    }
}

tsconfig.json:

{
    "compilerOptions": {
        "outDir": "build",
        "rootDir": ".",
        "alwaysStrict": true,
        "charset": "UTF8",
        "diagnostics": false,
        "experimentalDecorators": true,
        "keyofStringsOnly": true,
        "noEmitHelpers": true,
        "noImplicitAny": true,
        "noImplicitReturns": true,
        "noImplicitThis": true,
        "noUnusedLocals": true,
        "removeComments": false,
        "skipDefaultLibCheck": true,
        "skipLibCheck": true,
        "sourceMap": false,
        "strictBindCallApply": true,
        "suppressImplicitAnyIndexErrors": true,
        "target": "es5",
        "types": []
    },
}

output

C:\test>node_modules\.bin\tsc -version
Version 3.7.3

C:\test>node_modules\.bin\tsc
test.ts:3:18 - error TS7022: 'b' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.

3  public readonly b = new B( this );
                   ~


Found 1 error.

It works with typescript 3.3.3.
It starts to fail with typescript 3.4.1.

The error happened when migrating our codebase from Typescript 3.2.2 to 3.7.3

The only solution I have for the moment is to change the line to

public readonly b: B = new B( this );

Please only log bugs when a circularity error is issued when there isn't a circularity. These examples both contain plainly-visible cycles and the error is correct.

Hm, ok, I just commented out because

  1. it was working on a previous version and so need to migrate a 1.5 million LOC typescript codebase
  2. the build randomly fails: at first, I had no build error on my code base, i had some flacky errors in my CI builds (unfortunately, unable to reproduce) (could the error depend on the order of the files are processed? > see below)
  3. The cycle is not that obvious as type B does not depend at all of the parameters of its constructors and is not generic. Type B is straightforward, so is attribute b in Type A, so I was thinking for type A. Sorry for the misunderstanding.

Also some checks :

class A<T> { public readonly b = new B( this ); }
class B { constructor( a: A<any> ) { } }

fails, ok.

class A<T> { public readonly b = new B( this ); }

class B { 
    public readonly a: A<any>;
    constructor( a: A<unknown> ) {
        this.a = a;
    }
}

fails, ok.
But, if I change the order of declarations of A and B:

class B { 
    public readonly a: A<any>;
    constructor( a: A<unknown> ) {
        this.a = a;
    }
}

class A<T> { public readonly b = new B( this ); }

No build error.

class B { 
    public readonly a: A<any>;
    constructor( a: A<any> ) {
        this.a = a;
    }
}

class A<T> { public readonly b = new B( this ); }

Fails (replaced _unknown_ by _any_)

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)

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;
    });
};

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;
...

Here's another snippet to reproduce it.

function setter<T>() {
    return <P extends keyof T>(key: P, handler: (self: T, val: T[P]) => void) => {
        return function(this: T, x: T[P]) {
            // complex stuff happens here
            handler(this, x);
        };
    };
}

class Foo {
    readonly a: string = ''
    setA = setter<Foo>()('a', (self, val) => self)
}

Playground

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.

I came across this when the services in my backend needed to reference each other.

Playground

Was this page helpful?
0 / 5 - 0 ratings

Related issues

sandersn picture sandersn  路  265Comments

born2net picture born2net  路  150Comments

jonathandturner picture jonathandturner  路  147Comments

chanon picture chanon  路  138Comments

blakeembrey picture blakeembrey  路  171Comments