Skip to content

"Expando assingment" for Javascript namespace stopped working. #49944

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
mpawelski opened this issue Jul 19, 2022 · 4 comments · Fixed by #50487
Closed

"Expando assingment" for Javascript namespace stopped working. #49944

mpawelski opened this issue Jul 19, 2022 · 4 comments · Fixed by #50487
Labels
Bug A bug in TypeScript Help Wanted You can do this
Milestone

Comments

@mpawelski
Copy link

mpawelski commented Jul 19, 2022

Looks that the "declare a namespace with a function inside and expand the function with expando assignments" workaround for my "JS namespaces" stopped working in TS 4.5

💻 Code

declare namespace app {
  function foo(): void;
}

app.foo.bar = (function () {
  const someFun = (arg: number) => {};
  return { someFun };
})();

app.foo.bar.someFun(1);

This works in 4.4.2.
This doesn't work in 4.5.5

🙁 Actual behavior

Compiler error:

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

🙂 Expected behavior

No error with proper inference.

@RyanCavanaugh RyanCavanaugh added the Bug A bug in TypeScript label Jul 19, 2022
@RyanCavanaugh RyanCavanaugh added this to the Backlog milestone Jul 19, 2022
@RyanCavanaugh RyanCavanaugh added the Help Wanted You can do this label Jul 19, 2022
@babakks
Copy link
Contributor

babakks commented Jul 23, 2022

@mpawelski I just tested with v4.7.3 and it worked (compiled with no error) and this is the resulting JS file:

app.foo.bar = (function () {
    var someFun = function (arg) { };
    return { someFun: someFun };
})();
app.foo.bar.someFun(1);

@mpawelski
Copy link
Author

mpawelski commented Jul 26, 2022

@babakks
It does compile when I don't use --noImplicitAny option.
But then app.foo.bar is any which is not the case in 4.4.2 where I get proper type inference and validation

@babakks
Copy link
Contributor

babakks commented Aug 1, 2022

@RyanCavanaugh I tried to debug/compare on both the current dev (v4.8) and v4.4.2.

At last, it seems like the problem has something to do with the getContextualReturnType function where it checks for immediately-invoked-function-expressions (this section was not present at v4.4.2):

function getContextualReturnType(functionDecl: SignatureDeclaration, contextFlags: ContextFlags | undefined): Type | undefined {
// ...
      const iife = getImmediatelyInvokedFunctionExpression(functionDecl);
      if (iife) {
            return getContextualType(iife, contextFlags);
      }
// ...
}

When the control reaches this, iife is assigned with a truthy value (returned from the getImmediatelyInvokedFunctionExpression function) and then the getContextualType function is called, which then tries to resolve the return type of the immediately-invoked function expression by walking up the nodes tree and finally reaching the bar symbol, again (because it was all started when bar was first encountered by the checker). So, the circular relationship is concluded.

To confirm this I introduced a new parameter to the getContextualReturnType to let it optionally skip the IIFE contextual type checking. It worked and the circular reference errors were gone. Obviously, this is not a viable solution and I don't know what can be done.

@mpawelski One workaround for this, though I'm sure you've already found that, is to separate the function definition and the invocation like this:

declare namespace app {
    function foo(): void;
}

function expand() {
    const someFun = (arg: number) => { };
    return { someFun };
}
app.foo.bar = expand();

app.foo.bar.someFun(1);

@Zzzen
Copy link
Contributor

Zzzen commented Aug 28, 2022

It's not related to IIFE, a simple object literal have the same error. playground

declare namespace app {
  function foo(): void;
}

// 'bar' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.(7022)
app.foo.bar = {
  someFun(arg: number) {}
};

app.foo.bar.someFun(1);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript Help Wanted You can do this
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants