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
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
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)
}
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:
| undefined is removed on type of aa = b; is removed or inlined to a = await getAString(a);getAString is not asyncI 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 have a similar https://github.com/microsoft/TypeScript/issues/40279
I came across this when the services in my backend needed to reference each other.
Most helpful comment
Having similar issue with very simple example
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.