Skip to content

[typeBaselines] Recursive type gets erased to any immediately #523

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
JsonFreeman opened this issue Aug 25, 2014 · 9 comments · Fixed by #550
Closed

[typeBaselines] Recursive type gets erased to any immediately #523

JsonFreeman opened this issue Aug 25, 2014 · 9 comments · Fixed by #550
Assignees
Labels
Bug A bug in TypeScript Fixed A PR has been merged for this issue

Comments

@JsonFreeman
Copy link
Contributor

Test case: tests/cases/compiler/declFileTypeofFunction.ts

var b: () => typeof b;

b : any
b : any

We expect both of these to be () => any

@ahejlsberg
Copy link
Member

I'm not sure by what logic you would expect the resulting type to be () => any. The only two choices that make sense to me are any or an infinitely expanding type () => () => () => .... After all, you'd expect the type of b to be the same as b(). The compiler's current choice of any is consistent with var b = () => b; which also gives type any to b (as per the spec). If there is a bug here I think it is in the old compiler.

@sophiajt
Copy link
Contributor

What about:

var b: () => {next: typeof b; data: number};

It seems to me that neither 'any' or '()=>any' is what the developer intended. I'm also wondering how this interacts with the recursive type literal proposal (#517). Using the typeof b like the above just makes me think it's a poor man's version of the recursive type literal.

@JsonFreeman
Copy link
Contributor Author

Let me clarify. My understanding of the current state of things is the following:

  1. We erase the whole variable to any if the variable appears in its initializer and has no type annotation.
  2. If the variable appears in its type annotation, then we may give you a recursive type. But we only give a recursive type if the self-reference is in a property, not a signature return type. For example,
var a: { x: typeof a};

has a recursive type, but

var a: () => typeof a;
var b: { (): typeof b; };

do not.
3. Even if the type is recursive, we currently have another bug #463, whereby upon printing it, we unroll it one level, and then print any.

The present bug is addressing number 2 above. We should be consistent no matter how the type references the variable. Right now, the laziness in setObjectTypeMembers for properties ensures that recursive types will be allowed in property position. But in getSignatureFromDeclaration, we eagerly resolve the return type, causing us to witness ourselves needlessly. Note that this does not happen for function declarations.

@ahejlsberg
Copy link
Member

I think you're right, we shouldn't eagerly resolve a return type annotation in getSignatureFromDeclaration. I think it is pretty easy to fix, we're already set up to do deferred resolution of the return type through getReturnTypeOfSignature.

@JsonFreeman
Copy link
Contributor Author

Great! While we're at it, are there any other positions inside a type that need to be deferred like this to allow for recursive types? Indexers definitely. What about type parameter constraints in a signature? Type arguments in a type reference? We can test these to see if they behave correctly.

@sophiajt sophiajt added this to the TypeScript 1.2 milestone Aug 26, 2014
@ahejlsberg
Copy link
Member

Indexer types are already deferred. Ditto type parameter constraints. Type arguments in a type reference are not deferred--we deeply rely on the notion that a the identity of a type reference is computed from the identity of the generic type and the identity of each of the type arguments. So, typeof x and Foo<typeof x> are equally eager in their resolution, and that would be very hard to change.

@JsonFreeman
Copy link
Contributor Author

Hmm, that is unfortunate about the type arguments, particularly because we want the instantiation to just be a substitution of type arguments for type parameters. It should be synonymous with the expanded form.

Indexer types don't look deferred to me. getTypeFromTypeLiteralNode calls getIndexTypeOfSymbol, which calls getTypeFromTypeNode. So I think they have the same problem as call/construct signatures.

@ahejlsberg
Copy link
Member

I just realized that getTypeFromTypeLiteral eagerly populates the members of the type instead of deferring to resolveObjectTypeMembers. Guess I wasn't thinking of typeof when I implemented it, but it should be pretty easy to make it to be deferred. That would solve the problem for both return types and indexer types.

@JsonFreeman
Copy link
Contributor Author

Now that you mention it, yes, that seems like the most general solution.

@RyanCavanaugh RyanCavanaugh added the Fixed A PR has been merged for this issue label Sep 11, 2014
@microsoft microsoft locked and limited conversation to collaborators Jun 18, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Bug A bug in TypeScript Fixed A PR has been merged for this issue
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants