Skip to content
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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unrelated JSDoc annotation causes class to not be assignable to itself or iterable #53967

Closed
jakebailey opened this issue Apr 22, 2023 · 4 comments 路 Fixed by #55053
Closed

Unrelated JSDoc annotation causes class to not be assignable to itself or iterable #53967

jakebailey opened this issue Apr 22, 2023 · 4 comments 路 Fixed by #55053
Assignees
Labels
Bug A bug in TypeScript Fix Available A PR has been opened for this issue

Comments

@jakebailey
Copy link
Member

Bug Report

馃攷 Search Terms

jsdoc annotation iterable self assignable

馃晽 Version & Regression Information

  • This changed between versions 4.2 and 4.3

馃捇 Code

Here's a compiler repro that reliably works.

// @lib: es2017
// @allowJs: true
// @checkJs: true
// @noEmit: true

// @filename: index.js
const LazySet = require("./LazySet");

/** @type {LazySet<string>} */
const stringSet = new LazySet();

stringSet.addAll(stringSet);


// @filename: LazySet.js
// Comment out this JSDoc, and note that the errors index.js go away.
/**
 * @typedef {Object} SomeObject
 */
void 0;

/**
 * @template T
 */
class LazySet {
    /**
     * @param {Iterable<T>=} iterable
     */
    constructor(iterable) {
        /** @type {Set<T>} */
        this._set = new Set(iterable);
    }

    /**
     * @param {Iterable<T> | LazySet<T>} iterable
     * @returns {LazySet<T>}
     */
    addAll(iterable) {
        return this;
    }

    [Symbol.iterator]() {
        return this._set[Symbol.iterator]();
    }
}

module.exports = LazySet;

馃檨 Actual behavior

webpack/webpack#16957 (comment)

Argument of type 'LazySet<string>' is not assignable to parameter of type 'Iterable<string> | LazySet<string>'.
  Property '[Symbol.iterator]' is missing in type 'import("/home/jabaile/work/webpack-repro/lib/LazySet")<string>' but required in type 'import("/home/jabaile/work/webpack-repro/lib/LazySet")<string>'.ts(2345)
LazySet.js(27, 2): '[Symbol.iterator]' is declared here.

Note that LazySet is not assignable to LazySet, which is weird. Changing the signature of addAll to just be Iterable<T> is equivalent.

Also, if you actually construct this example in the editor, editing index.js will make the error go away temporarily, until tsserver is restarted, or LazySet.js is edited. Very odd.

馃檪 Expected behavior

No errors.

@alexander-akait @TheLarkInn

@sandersn
Copy link
Member

sandersn commented Jul 13, 2023

My initial guess is that the implicit export SomeObject turns the single export of LazySet.js into an export of a namespace, but in a non-standard incorrect way that doesn't issue the correct error.

Edit: The fact that the error can go away when rebuilding the checker implies that there may be a missing call to mergeSymbol somewhere in a JS-specific code path.

@sandersn
Copy link
Member

sandersn commented Jul 14, 2023

Slightly simplified repro (this version only has one call to make debugging easier):

// @lib: es2017
// @allowJs: true
// @checkJs: true
// @noEmit: true
// from #53967, based on webpack/webpack#16957

// @filename: index.js
const LazySet = require("./LazySet");

/** @type {LazySet} */
const stringSet = undefined;
stringSet.addAll(stringSet);


// @filename: LazySet.js
// Comment out this JSDoc, and note that the errors index.js go away.
/**
 * @typedef {Object} SomeObject
 */
class LazySet {
    /**
     * @param {LazySet} iterable
     */
    addAll(iterable) {}
    [Symbol.iterator]() {}
}

module.exports = LazySet;

sandersn added a commit to sandersn/TypeScript that referenced this issue Jul 17, 2023
Fixes microsoft#53967

The problem is that the weird, ad-hoc CJS merge needs to happen at two different
times.  The first is its existing location in getCommonJsExportEquals.
The second is getResolvedMembersOrExportsOfSymbol, which combines
normal, early-bound symbols with late-bound symbols.

The CJS merge happens before the addition of late-bound symbols, so a
merge will result in the loss of [Symbol.iterable] from the bug.
But manually merging the resolvedExports/Members during the CJS merge
also doesn't work, because it prevents the module's
resolvedExports/Members from being filled in later.

Instead, this PR delays part of the CJS merge to
getResolvedMembersOrExportsOfSymbol. Unfortunately, this requires
retrieving the original, unmerged symbols--since their
resolvedExports/Members are already cached. I do this by looking at the
symbols for each of the merged symbol's declarations, which feels wrong.

Please suggest alternate ways to do this. I don't think I understand the
late-binding machinery well enough, so it's likely there's a cleaner
way.
@typescript-bot typescript-bot added the Fix Available A PR has been opened for this issue label Jul 17, 2023
@regseb
Copy link

regseb commented Aug 31, 2023

Maybe I have a similar problem? The type from a JavaScript import is incompatible with a JSDoc import.

Files

  • package.json

    {
      "name": "testcase",
      "version": "1.0.0",
      "type": "module",
      "dependencies": {
        "typescript": "5.2.2"
      }
    }
  • foo.js

    export default class Foo {}
  • index.js

    import FooJs from "./foo.js";
    
    /**
     * @typedef {import("./foo.js")} FooJsDoc
     */
    
    /**
     * @type {FooJsDoc}
     */
    const foo = new FooJs();

To reproduce

  1. npm install
  2. npx tsc --allowJs --noEmit --checkJs index.js

Actual behavior

index.js:10:7 - error TS2741: Property 'Foo' is missing in type 'Foo' but required in type 'typeof import("/home/regseb/dev/testcase/foo")'.

10 const foo = new FooJs();
         ~~~

  foo.js:1:22
    1 export default class Foo {}
                           ~~~
    'Foo' is declared here.


Found 1 error in index.js:10

Edit: #55608

@sandersn
Copy link
Member

sandersn commented Sep 1, 2023

It could be related because of the @typedef, but you're using ES-standard imports, so it's probably different. I'd recommend filing a bug that references this one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript Fix Available A PR has been opened for this issue
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants