Skip to content

"implicit any" where circularity is trivially resolvable #49837

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
iwikal opened this issue Jul 8, 2022 · 7 comments
Closed

"implicit any" where circularity is trivially resolvable #49837

iwikal opened this issue Jul 8, 2022 · 7 comments
Labels
Not a Defect This behavior is one of several equally-correct options

Comments

@iwikal
Copy link

iwikal commented Jul 8, 2022

Bug Report

πŸ”Ž Search Terms

πŸ•— Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about Common "Bugs" That Aren't Bugs and When and why are classes nominal?

⏯ Playground Link

Playground link with relevant code

πŸ’» Code

class Foo {
  constructor(_provider: FooProvider) {}
}

interface FooProvider {
  getFoo(): Foo
}

class FooFactory {
  // 'getFoo' implicitly has return type 'any' because it does not have a
  // return type annotation and is referenced directly or indirectly in one of
  // its return expressions. (7023)
  getFoo() {
    // 'result' implicitly has type 'any' because it does not have a type
    // annotation and is referenced directly or indirectly in its own
    // initializer. (7022)
    const result = new Foo(this)
    return result
  }
}

πŸ™ Actual behavior

The type of the variable result, and thus the return type of FooFactory.getFoo,
defaults to any. This is wrong, because result clearly has the type Foo
regardless of the type of the argument to the Foo constructor.

Adding implements FooProvider to FooFactory does not help, which I also
find a bit strange.

πŸ™‚ Expected behavior

The circularity should be resolved at the new Foo(this) expression, since
calling this non-generic constructor can only result in one possible type,
regardless of arguments.

@MartinJohns
Copy link
Contributor

MartinJohns commented Jul 8, 2022

Your playground link does not work. Here's a working one: Playground link

Constructor argument of Foo requires the type FooProvider. You're passing this, so the compiler need to make sure that this is structurally ompatible with FooProvider. The type of this is not fully known yet, because the return type of getFoo() is inferred. In order to identify the return type it checks the body of the method, which returns result. The type of result depends on the assignment, where you create an instance of Foo. Here the compiler needs to make sure the argument you pass is compatible, and you're in an endless loop.

You can add an explicit return type annotation to your method, that will solve your issue.

Adding implements FooProvider to FooFactory does not help, which I also
find a bit strange.

Because that doesn't solve the issue: What type is this, specifically what return type getFoo() has. The interface you implement does not specify the types of the method, it only makes sure your class is compatible with the interface. You can have an interface that say "returns number | string", and an implementation that only returns string.

@iwikal
Copy link
Author

iwikal commented Jul 8, 2022

My point is that the compiler does not need to know if the this argument can be passed to new Foo before it knows the return type of Foo. It could assume that the return type is Foo and move on to the next statement, as long as it remembers to later check that FooFactory is assignable to FooProvider once we know exactly what a FooFactory is.

@MartinJohns
Copy link
Contributor

MartinJohns commented Jul 8, 2022

That would require the compiler to perform type checks in two iterations (first inference, then the rest), which it currently not does. The issue exists for a long time already, and Ryan mentions the issue in this comment.

The best approach is really to just add a type annotation.

@iwikal
Copy link
Author

iwikal commented Jul 8, 2022

Alright, close as wontfix then I guess? Or is it possible that this could be implemented at some point in the future? Is there an open issue that tracks this?

@RyanCavanaugh RyanCavanaugh added the Not a Defect This behavior is one of several equally-correct options label Jul 8, 2022
@RyanCavanaugh
Copy link
Member

If someone can make this work without making a bunch of other stuff (perf, maintainability, etc) worse they're free to send a PR, but we don't think that's possible.

@github-actions
Copy link

github-actions bot commented Jun 8, 2023

This issue has been marked as 'Not a Defect' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

@github-actions github-actions bot closed this as not planned Won't fix, can't repro, duplicate, stale Jun 8, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Not a Defect This behavior is one of several equally-correct options
Projects
None yet
Development

No branches or pull requests

4 participants