microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
101.25k stars 12.52k forks source link

The inferred type of "X" cannot be named without a reference to "Y". This is likely not portable. A type annotation is necessary. #42873

Closed mistic closed 7 months ago

mistic commented 3 years ago

Bug Report

šŸ”Ž Search Terms

inferred type cannot be named, symlink node_modules

šŸ•— Version & Regression Information

I'm verifying the problem on the typescript@4.1.3. I've not tried older versions but at least is also reproducible on the @next version as of today.

It is probably a regression or a corner case related with other issues opened and already closed like:

āÆ Playground Link

Link for a repo where the problem is being reproduced

NOTE: Just clone the repo and run yarn tsc

šŸ’» Code

All the relevant code can be found at https://github.com/mistic/reproduce-typescript-problem-when-symlinking-node_modules

It is just reproducing a similar setup that I had on other project that was generating the problem:

šŸ™ Actual behavior

I got the following error:

error TS2742: The inferred type of 'Nav' cannot be named without a reference to '../../deps/node_modules/@types/react-router'. This is likely not portable. A type annotation is necessary.

8 export const Nav = withRouter(({ history }: NavProps) => {
               ~~~

Found 1 error.

šŸ™‚ Expected behavior

I was expecting no error at all and that the typescript compiler was just able to find all the respective modules. I've tried everything that I thought was related like enabled the preserveSymlinks. The only thing that stops the error is importing the withRouter type directly from react-router and not from react-router-dom but that doesn't make very sense because I actually want to use the react-router-dom on a couple of places.

\cc @weswigham @sheetalkamat @andrewbranch because I saw you previously worked to try to solve similar issues.

weswigham commented 3 years ago

Hm, it's a bit awkward in your case, since we technically never directly discover that react-router is directly visible from your project folder (our symlink discovery is a bit heuristic based, so we only find them in paths we had to resolve a symlink across to access). If you had a react-router import somewhere in the file already, we'd know we have a lookup available... Hm. @RyanCavanaugh today we don't use a package.json for anything within a TS project, however with the recent changes to node resolution, we really should start considering it (so we can resolve package self names and "imports" maps) - as part of that, we could maybe consider using the dependencies manifest as a list of safe-to-access modules, like how we already use it for autoimports in the language server.

mistic commented 3 years ago

@weswigham Thanks for looking into the issue. What you wrote makes sense in my head as an explanation about why it is this bug happening. Let me know if I can be useful in any other way to move forward with this one as I really hope we can fix it!

paynecodes commented 3 years ago

I've got a repro which may help with the fix available here: paynecodes/ts-repro-type-annotation-require-symlinks. I don't want to hijack this issue if it's the wrong place, so let me know if a new issue is desirable.

The README lists references to similar issues I was able to dig up.

btakita commented 3 years ago

I have a set of npm packages where "types" points to a .ts (not a .d.ts) file. Reason being I can directly jump to the implementation with the IDE. The issue I'm running into is the following:

An npm package B that depends on another npm package A. A factory function obj_ that returns a value with a custom type defined or referenced by pkg A. pkg B has export const obj = obj_(). Typescript compilation is successful. When another package C imports the obj from pkg B, The inferred type of "obj" cannot be named without a reference to "path/to/pkg/A". This is likely not portable. A type annotation is necessary.

This cannot happen. At the very least, it would be useful to have a setting in tsconfig.json for the TS compiler to make pkg B fail compilation unless const obj = obj_() has a type annotation. Otherwise, the pkg B has to be fully built & installed by another pkg C as an npm package to test. Inferring the type across packages (not in rootDir) would be the more ergonomic solution, as long as performance does not suffer.

weswigham commented 3 years ago

Ā At the very least, it would be useful to have a setting inĀ tsconfig.jsonĀ for the TS compiler to make pkgĀ BĀ fail compilation unlessĀ const obj = obj_()Ā has a type annotation.Ā 

There is one. declaration: true.

btakita commented 3 years ago

My understanding of declaration: true is that a *.d.ts file is created. It would be great to navigate directly to the *.ts source.

To support both .ts & .d.ts files in an npm package, something like "declarationDir": "dist" is necessary, otherwise, *.ts* files will be imported due to import precedence.

Another minor issue that I have to account for is with single file components which are compiled separate from Typescript. For example a svelte or vue component. These components will need to be copied over to the dist directory during npm run build, since npm does not support links within packages.

These are both minor but annoying issues. Particularly annoying when working with monorepos/multirepos with many packages. It's rare & tooling can fix the build issues. It seems like *.d.ts is useful in having a standard to ensure that type inference works in all cases, but it unfortunately breaks navigation. Of course the tools (VSCode, Jetbrains) would need to address the navigation problem as it stands.

Would it make sense to annotate the path to the source of the .d.ts file in a comment, like a .map file?

weswigham commented 3 years ago

My understanding ofĀ declaration: trueĀ is that aĀ .d.tsĀ file is created. It would be great to navigate directly to theĀ .tsĀ source.

Also set declarationMap: true.

nwaughachukwuma commented 3 years ago

Check if this works for you: https://github.com/microsoft/TypeScript/issues/29808#issuecomment-540292885

mistic commented 3 years ago

@weswigham @btakita not related with that initial issue but with your last discussion: declarationMap has also a buggy behaviour when used along sourceRoot (which is vital in some cases) https://github.com/microsoft/TypeScript/issues/31873 we are still waiting for a solution on that other issue.

riordanpawley commented 3 years ago

Check if this works for you: #29808 (comment)

doesn't help in my case. yarn workspaces monorepo setup with monorepo/pkg1 depending on monorepo/pkg2 & node_modules/pkg1 monorepo/pkg2 depending on node_modules/pkg1 Using same version of external/pkg1

dustinlacewell commented 3 years ago

I get this issue in a Rush monorepo where a project depends on cmd-ts. Strangely, it only affects VSCode. I have to do the path alias trick, restart TS, but then it works. An I can remove the path alias. Very strange.

paynecodes commented 3 years ago

I get this issue throughout many scenarios when using a Rush monorepo. Rush, by default, uses pnpm (pnpm workspaces is recommended). I haven't been able to track down the root cause, but I have scoured this issue tracker and subscribed to many topics hoping someone squashes this.

InsOpDe commented 3 years ago

Im using a pnpm monorepo and had a similiar problem which I could resolve by setting baseUrl: "." inside the package tsconfig.json

3cL1p5e7 commented 2 years ago

In my case: When I use declaration: true and yarn as package manager -> got same error cannot be named without a reference to 'styled-components/node_modules/@types/react After researching I found mismatch versions of @types/react for my repo and dependency styled-components/node_modules/@types/react

If run yarn install --flat, manually fill subpackage resolutions and run yarn build - problem is disappear

@InsOpDe pnpm also installs flat dependencies for monorepo with resolutions and you avoided my problem :)

imcuttle commented 2 years ago

In my case, add preserveSymlinks for resolve it

{
  "compilerOptions": {
    "preserveSymlinks": true
  }
}
zhyd1997 commented 2 years ago

@mistic try this: add below code into this file: https://github.com/mistic/reproduce-typescript-problem-when-symlinking-node_modules/blob/main/project/tsconfig.json

{
 "paths": {
   "@types/react-router": ["../../deps/node_modules"]
 },
}

it works!

OoDeLally commented 2 years ago

In my case, add preserveSymlinks for resolve it

{
  "compilerOptions": {
    "preserveSymlinks": true
  }
}

This solved it for me too, but why ? :thinking:

andre-byrne commented 2 years ago

Is there any way to designate parts of your code where this error is ignored? I'm getting this error, but in a part of my code that will never be exported for an end user (in private code, I mean).

tstordyallison commented 2 years ago

FWIW, I'm getting this when compiling https://github.com/facebook/flipper on Windows, where I have my code on drive ("I:") that is actually just symlinked (or whatever that is on Windows) to a directory on the "C:" drive.

Running the compile from the actual "C:" location makes this error go away. Strange.

Using preserveSymlinks didn't make any difference for me.

nemosmithasf commented 2 years ago

For my case, I've found the following:

It only occurs when "declaration": true (no idea why however)

You can work around it by either:

  1. Using paths:{} in compilerOptions
  2. Deleting the problematic package from the node_modules folder of the symlink'd location (not practical but it does fix the problem)
  3. Publishing and install via NPM
af4oz commented 2 years ago

I removed "declaration": true , "preserveSymlinks": true" and restart ts server

vjpr commented 2 years ago

I'm using pnpm and I made sure that I only had one version of react-query (in my case) across the entire codebase and it started working, restarted TS server, and added declarationMap: true. So one of these fixed it...

wmzy commented 2 years ago

I use pnpm.overrides remove the duplicate dependencies and resolved this problem.

vtereshyn commented 2 years ago

"preserveSymlinks": true doesn't resolve all errors that I have. I also use pnpm and typescript together. Previously I used yarn and typescript and everything was fine.

Now, I have The inferred type of 'someVar' cannot be named without a reference to '@pnpm-test/core-config/node_modules/yup/lib/string'. This is likely not portable. A type annotation is necessary. and have no idea how to resolve that.

This appears if I set

"baseUrl": "./",
"paths": {
            "*": [
                "node_modules/*/"
            ]
        }

If I remove that, I also have the same issue but in other places.

The way to fix this exact problem described above was setting path to exact module (yup in my case), but then I still get other similar errors. That's super frustrating.

I also set declaration: false for testing purposes but had no luck having it working.

jsheely commented 2 years ago

I was able to setup my repository so that it works with PNPM and NPM for installation in a monorepo workspace. I've found that this bug happens with PNPM but goes away with NPM

root

Appears to be a bug in dependency resolution. Appears to be related to how PNPM does symbolic folder links. This specifically happens when I expose a type from a child dependency.

As long as all the types in the sub module are internal to that module it works fine. But if I reference a type from a sub/sub module and then build it will fail

For science I've also tried pnpm i --shamefully-hoist and it fails the same.

None of the band aides mentioned above have seemed to resolve it for me using PNPM.

Obviously disable declaration resolves the build. But the whole point is to create the index.d.ts declaration file that is blowing up

It fails consistently in all forms:

tbergquist-godaddy commented 2 years ago

I managed to get this working. The steps would be to install all troublesome node_modules in the root package.json, then add this (in my case) to tsconfig.json at the root

"paths": {
      "react": ["node_modules/@types/react"], // fair I am using this
      "react-query": ["node_modules/react-query"], // fair I am using this
      "csstype": ["node_modules/csstype"] // this is causing issues from within react using CSSProperties
    }

And this makes sense due to the nature of pnpm, it installs the actual files into node_modules/.pnpm/<package>@<version>/node_modules, then symlinks this into each workspace. You cannot guess this path, hence installing the packages into the root package.json will have them symlinked into the node_modules at the root, and you can reference them through paths.

I am not sure if this is a typescript issue or a pnpm issue, but I do think having to go through those steps is sub-optimal šŸ˜ž

jsheely commented 2 years ago

@tbergquist-godaddy Not sure I understand. In my setup I'm referencing a package (A) within my mono repo into another package (B) in the same folder.

(A) References a third party dependency that is the one being referenced as an error in (B) when I import A and use it

In my case PNPM installs a symbolic link of (A) into (B) node_modules. (A) then contains a node_modules symbol link to the third party package

But as far as TSC should be concerned this is standard dependency hierarchy and it should be able to find all the typings.

tbergquist-godaddy commented 2 years ago

Well, from the error I get The inferred type of 'useSaveLanguage' cannot be named without a reference to 'packages/whitelabel/node_modules/react-query/types. And this error is coming from packages/language/.... which gets its types from packages/language/node_modules/react-query/types.

Both of these might be symlinked to the same type, but I take it that tsc doesn't follow symlinks and see the paths as different path and then types.

jsheely commented 2 years ago

@tbergquist-godaddy Oh that's interesting that yours references the folder path fully. Mine references the module name then path

The inferred type of 'Ethereum' cannot be named without a reference to '@decentology/hyperverse-evm/node_modules/@rainbow-me/rainbowkit'. This is likely not portable. A type annotation is necessary.

It's frustrating that this problem goes away when I use npm install beause while I don't get this error I have other issues that require that I use PNPM. So it leads me to believe that it's just a setting away in PNPM to resolve the error

jsheely commented 2 years ago

Upon further trial and error. I can confirm that setting the path in tsconfig.json does in fact work

However I'm not sure I understand why

Here is how I fixed it:

I created a .npmrc in the package (B) that was having the problem and set. Which is nice because I can localize what needs hoisted per package: Reference: https://pnpm.io/npmrc#public-hoist-pattern EDIT: I thought .npmrc in sub folder worked. Seems that may have been a fluke. Adding it to root .npmrc more reliable

public-hoist-pattern[]=*@rainbow-me*
public-hoist-pattern[]=*@ethersproject*

Which were the two projects that had errors with the The inteferred type....

I then ran pnpm i to install the modules. This lifted thw two packages into the root mono repo node_modules folder

I then went back to package (B) and configured the tsconfig.json as others have suggested to point to that path. Reference: https://www.typescriptlang.org/tsconfig#paths

"paths": {
    "@rainbow-me/rainbowkit": ["../../node_modules/@rainbow-me/rainbowkit"],
    "@ethersproject/providers": ["../../node_modules/@ethersproject/providers"],
}

No more error. Yay!

So it's worth noting that the symbolic link path originally would have been nested like

node_modules/@decentology/hyperverse-evm/node_modules/@rainbow-me/rainbowkit

But when setting that nested path in my trial and error in the tsconfig.compilerOptions.path it fails to resolve the typings. But hosting it to the top resolves the problem. I assume the nested path would be the default path it would be using :shrug:

tbergquist-godaddy commented 2 years ago

Yeah, using public-hoist-pattern will also work, since it essentially does the same as adding it to your package.json folder, it will symlink it to the root node_modules of your workspace.

It might be the preferred approach since you don't have to stick dependencies you are not directly depending on in your package.json file.

jgoux commented 2 years ago

I have this error with @trpc/server in a monorepo, using yarn@3.2.1.

I tried all the solutions in this issue and none worked. šŸ˜­

I've made an issue on trpc repo with a repro : https://github.com/trpc/trpc/issues/1945

edit

It seems like I have to use a wildcard in the paths and it solves the issue āœ…

{
  "extends": "@acme/tsconfig/node",
  "compilerOptions": {
    "baseUrl": "src",
    "outDir": "./dist",
    "rootDir": "./src",
    "paths": {
      "@trpc/server/*": ["../../../node_modules/@trpc/server/*"]
    }
  },
  "include": ["src/**/*.ts"]
}

Like @jsheely I don't understand why it works that way and if it's a typescript resolution issue or if it's a misuse of typescript at the library level.

DrJume commented 2 years ago

In a Vue 3 app I commented out "composite": true under compilerOptions, because it sets declaration: true

vtereshyn commented 2 years ago

In a Vue 3 app I commented out "composite": true under compilerOptions, because it sets declaration: true

this will break your composite build.

DrJume commented 2 years ago

In a Vue 3 app I commented out "composite": true under compilerOptions, because it sets declaration: true

this will break your composite build.

Thank you for the notice. But what exactly is a composite build?

vtereshyn commented 2 years ago

In a Vue 3 app I commented out "composite": true under compilerOptions, because it sets declaration: true

this will break your composite build.

Thank you for the notice. But what exactly is a composite build?

It allows you to skip the build process if your project has been already built.

The composite option enforces certain constraints which make it possible for build tools (including TypeScript itself, under --build mode) to quickly determine if a project has been built yet.

https://www.typescriptlang.org/tsconfig#composite

DrJume commented 2 years ago

In a Vue 3 app I commented out "composite": true under compilerOptions, because it sets declaration: true

this will break your composite build.

Thank you for the notice. But what exactly is a composite build?

It allows you to skip the build process if your project has been already built.

The composite option enforces certain constraints which make it possible for build tools (including TypeScript itself, under --build mode) to quickly determine if a project has been built yet.

https://www.typescriptlang.org/tsconfig#composite

Thank you. It seems the option "composite": true is only present in the generated Vue 3 app boilerplate if vitest is used.

See: https://github.com/vuejs/create-vue-templates

It isn't included in the tsconfig.json in a regular typescript setup. I don't think it will cause problems as it seems to be only recommended with vitest.

stevebeauge commented 2 years ago

Reading this long thread, I understand the root cause is somehow related to symbolic links.

I just don't get why TS compiler should care. Isn't it simply of operating system behavior ? Shouldn't TS compiler just "blindly" read the file, whether it's linked or not ?

dexy58 commented 2 years ago

The inferred type of 'authSlice' cannot be named without a reference to '.pnpm/immer@9.0.14/node_modules/immer/dist/internal'. This is likely not portable. A type annotation is necessary.

This is what I get. I'm using pnpm, t ypescript and monorepo. To be precise, I'm using this: https://github.com/NiGhTTraX/ts-monorepo

authSlice is actually createSlice, and createSlice is imported from @reduxjs/toolkit. I have read the entire thread and setting the baseUrl = "." didnt work.

EDIT: I also added

"paths": {
            ".pnpm/immer@9.0.14/*": ["node_modules/.pnpm/immer@9.0.14/*"]
        },

but that also didn't work.

EDIT 2: I managed to fix the error by deleting

    "declaration": true,
    "declarationMap": true,

from root tsconfig.build.json

atsikov commented 2 years ago

I made another small repo, based on rush and pnpm. It consists of two packages, and declarations are generated for both with rush build:dts, similar to what I have in a bigger project.

https://github.com/atsikov/ts-not-portable-error

Running the command throws an error for the second package

src/index.ts(3,14): error TS2742: The inferred type of 'useRunMutation' cannot be named without a reference to '../../common/node_modules/react-query/types'. This is likely not portable. A type annotation is necessary.

Both packages use react-query as a dependency, however for the common it is a peer (added to dev as well), as this package is not supposed to be used on its own. Generated declarations for common contain import('react-query').{SomeType} and it is the reason for "cannot be named" error. I would expect TS to try to resolve react-query relatively to both definitions package (where the dependency is not installed because of being a peer) and to the hosting package (where the dependency exists). It looks like TS only tries to perform a former resolution and fails to do so.

This was probably already mentioned somewhere, but I didn't find a clear explanation. It is some design limitation, or was it done on purpose, or just something that was not considered?

joeljeske commented 2 years ago

This related issue #37960 proposes a new option in tsconfig to bypass this compile time check. This new options would be useful for incremental builds, where the declaration files are not shipped as artifacts (e.g. monorepo, workspaces, etc...).

That issue includes a patch that could be applied to "typescript". I'd be interested if it fixes the issues described here.

atablash commented 2 years ago

That issue includes a patch that could be applied to "typescript". I'd be interested if it fixes the issues described here.

Didn't work for me šŸ˜­

alifatic commented 2 years ago

any body has a solution?

matthewlilley commented 2 years ago

human err

jonlu commented 2 years ago

Since this has to do with hoisting, switching the function declarations from const to function resolved this for me. I encountered this when exporting the configurations for my redux store

Before:

The inferred type of 'setupStore' cannot be named without a reference to 'XXX'. This is likely not portable. A type annotation is necessary.

export const setupStore = (preloadedState?: PreloadedState<RootState>) => {
return configureStore({...});
}

After: (no errors)

export function setupStore (preloadedState?: PreloadedState<RootState>) {
  return configureStore({...});
}
suleyman-tekin commented 2 years ago

I managed to fix this by adding "declaration": false to compilerOptions in tsconfig.json for my package, adding it here in case it helps someone else.

ekkolon commented 2 years ago

There is a related issue (#29808) as implicitly pointed out by @nwaughachukwuma in his comment. I solved it after following @akash-rajput\'s answer within the same thread.

To quote:

Happens when we have conflicting versions of a package installed. A temporary solution would be to remove the package mentioned from peer dependencies of the plugin giving the error.

Or install the exact version of common dependency

In my case, I had linked a local NPM package with peer dependency \@nestjs/common\@^9.0.7 to a monorepo that had \@nestjs/common\@^8.0.0 installed.

I'm using typescript@4.7.2.

NeurAlch commented 2 years ago

In my case, add preserveSymlinks for resolve it

{
  "compilerOptions": {
    "preserveSymlinks": true
  }
}

This worked for me.

Note: I had to restart my TypeScript Server for this to have an effect on my IDE

GiancarlosIO commented 2 years ago

thanks @PabloRosales, that worked for me šŸ˜„ Just one question, do you know what side effects we can have in the typecheck if we enable that? šŸ¤” is it safe?

NeurAlch commented 2 years ago

@GiancarlosIO I had this problem when using pnpm, switching to npm actually solved it for me without needing to use this anymore. The option looks safe if you need it: docs didn't experience any issue on my side.