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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixing namespace import debug failure #59004

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

navya9singh
Copy link
Member

Fixes #58740
This debug failure happened when trying to resolve a namespace import. As the identifier for the import did not have a parent, an error was thrown.

@typescript-bot typescript-bot added Author: Team For Milestone Bug PRs that fix a bug with a specific milestone labels Jun 24, 2024
@navya9singh navya9singh changed the title Error for namespace import Error for namespace import debug failure Jun 24, 2024
@navya9singh navya9singh changed the title Error for namespace import debug failure Fixing namespace import debug failure Jun 24, 2024
Copy link
Member

@andrewbranch andrewbranch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the identifier for the import did not have a parent

This isn’t quite right—the symbol the identifier for the import resolved to doesn’t have a parent, because the symbol is the module symbol, not an export of a module. That’s what namespace imports are—imports that bind an identifier to an entire module namespace record. So while your draft implementation looks pretty good, there are two problems: (1) addImportFromExportedSymbol is not the right function to contain it, since the symbol being passed in is not an export, and (2) there are other ways an import/require can bind to a module symbol that are still broken. For example, I just changed in pasteEdits_namespaceImport.ts

- import * as test from "./a";
+ import test = require("./a");

and replicated the same crash. The same thing can also happen with a JavaScript require, as well as with a default import under certain conditions.1 The detection you need to determine if the symbol you have is for a module symbol is the isExternalModuleSymbol function instead of relying on looking at the syntax of referenceImport. However, I think you should maybe move your new code into a new ImportAdder function instead of expanding addImportForExportedSymbol.

Footnotes

  1. Test case
    /// <reference path="../fourslash.ts" />
    
    // @Filename: /folder/c.ts
    //// [||]
    
    
    // @Filename: /a.ts
    //// const abc = 10;
    //// const def = 20;
    //// export interface testInterface {
    ////     abc: number;
    ////     def: number;
    //// }
    
    // @Filename: /b.mts
    //// import test from "./a.js";
    ////
    //// [|function foo(abc: test.testInterface, def: test.testInterface) {
    ////    console.log(abc);
    ////    console.log(def);
    //// }|]
    //// 
    
    // @Filename: /tsconfig.json
    ////{ "compilerOptions": { "module": "nodenext" }, "files": ["/folder/c.ts", "a.ts", "b.mts"] }
    
    const range = test.ranges();
    verify.pasteEdits({
        args: {
            pastedText: [`function foo(abc: test.abc, def: test.def) {
    console.log(abc);
    console.log(def);
    }`],
            pasteLocations: [range[0]],
            copiedFrom: { file: "b.mts", range: [range[1]] },
        },
        newFileContents: {
            "/folder/c.ts":
    `import * as test from "../a";
    
    function foo(abc: test.abc, def: test.def) {
    console.log(abc);
    console.log(def);
    }`
        }
    });
    
    

for (const e of declaration.importClause.namedBindings.elements) {
const symbol = checker.getSymbolAtLocation(e.propertyName || e.name);
if (isNamespaceImport(declaration.importClause.namedBindings)) {
const symbol = checker.getSymbolAtLocation(declaration.importClause.namedBindings.name);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there’s no need for checker.getSymbolAtLocation anywhere in this function. Declarations always have a .symbol; you can just do declaration.importClause.namedBindings.symbol here, and .symbol on each ImportSpecifier below. Even the existing variable declaration / binding pattern stuff further down can lose the getSymbolAtLocation.

@@ -356,6 +357,24 @@ function createImportAdderWorker(sourceFile: SourceFile | FutureSourceFile, prog
}
}

function addImportForExternalModuleSymbol(symbol: Symbol, originalSymbol: Symbol, isValidTypeOnlyUseSite: boolean) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

symbol/originalSymbol are confusing names here. It looks like originalSymbol is the actual module symbol, and symbol is just an existing alias for the module that’s only being used to see what name to use. Instead, try just passing in the module symbol and the name you want to use?

@@ -356,6 +357,24 @@ function createImportAdderWorker(sourceFile: SourceFile | FutureSourceFile, prog
}
}

function addImportForExternalModuleSymbol(symbol: Symbol, originalSymbol: Symbol, isValidTypeOnlyUseSite: boolean) {
const useRequire = shouldUseRequire(sourceFile, program);
if (originalSymbol.valueDeclaration) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this check for? When does an external module symbol not have a valueDeclaration?

createModuleSpecifierResolutionHost(program, host),
);
const info: FixInfo = {
fix: { kind: ImportFixKind.AddNew, importKind: ImportKind.Namespace, addAsTypeOnly: isValidTypeOnlyUseSite ? AddAsTypeOnly.Allowed : AddAsTypeOnly.NotAllowed, useRequire, moduleSpecifierKind: undefined, moduleSpecifier },
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since there may be multiple different forms of import that can reference a whole module, you were asking about whether it’s ok to unconditionally coerce them into a namespace import. That might be fine, but it seems like it would make sense for this function to take an optional referenceImport like addImportFromExportedSymbol, and copy its kind and type-only status where appropriate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Author: Team For Milestone Bug PRs that fix a bug with a specific milestone
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Debug failure on paste with imports
3 participants