TypeScript icon indicating copy to clipboard operation
TypeScript copied to clipboard

"implicit any" where circularity is trivially resolvable

Open iwikal opened this issue 3 years ago • 6 comments

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.

iwikal avatar Jul 08 '22 16:07 iwikal

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.

MartinJohns avatar Jul 08 '22 16:07 MartinJohns

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.

iwikal avatar Jul 08 '22 16:07 iwikal

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.

MartinJohns avatar Jul 08 '22 16:07 MartinJohns

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?

iwikal avatar Jul 08 '22 17:07 iwikal

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.

RyanCavanaugh avatar Jul 08 '22 17:07 RyanCavanaugh

probably related: https://www.typescriptlang.org/play?jsx=0&ts=4.8.4#code/PTAEE8FMGcBpQC4AsCW1RtAQ1AOwK4C2ARpAE6gAUADmSoSgigG6QCUAsAFALjWShC4AHJFSFALygASpAT4yuACp9IAHl78A9gDNQO3ACYAfAG5u3TQJS4dNxpAAikSNVElyAQTJks4UFJC7uKgAD4YtvYITi5uYl4+fgDaALrmXNw6+LgAxkxauPq4lFgAXHjxZGzlNna4Ds6uwQm+-gDe3KBdoGRyCoVJ2KBqUgAMoAD8Q+UGJaAAtKAAjGzwOGncAL4WXCAYhNQANig5jIf+vfKK6AQeZPD40JAAJhGCIpWZ2XkoBUWGlGg5WgCDouAA5mxQB0uN0en1FEVKM0yIC2FCsOhsgBrXBaADuhUxFTu6U2QA adding return type number to fn2 fixes it, although the return type remains number: https://www.typescriptlang.org/play?jsx=0&ts=4.8.4#code/PTAEE8FMGcBpQC4AsCW1RtAQ1AOwK4C2ARpAE6gAUADmSoSgigG6QCUAsAFALjWShC4AHJFSFALygASpAT4yuACp9IAHl78A9gDNQO3ACYAfAG5u3TQJS4dNxpAAikSNVElyAQTJks4UFJC7uKgAD4YtvYITi5uYl4+fgDaALrmXNw6+LgAxkxauPq4lFgAXHjxZGzlNna4Ds6uwQm+-gDe3KBdoGRyCoVJ2KBqUgAMoAD8Q+UGJaAAtKAAjGzwOGncAL4WXCAYhNQANig5jIf+vfKK6AQeZPD40JAAJhGCIpWZ2XkoBUWGlGg5WgCDouAA5tUKndQB0uN0en1FEVKM0yIC2GxsOhsgBrXBaADuhSwN0q6U2QA

antonilol avatar Nov 02 '22 21:11 antonilol

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[bot] avatar Jun 08 '23 03:06 github-actions[bot]