TypeScript: types in transitive dependencies causing errors after upgrade from 3.1: The inferred type of ... cannot be named without a reference to
TypeScript Version: 3.4.3
Search Terms: transitive dependency inferred type cannot be named without a reference Code
Sorry that this is a Dockerfile, since the problem relates to multiple modules being installed as dependencies to each other it’s difficult to express in a single piece of code. There’s only 16 lines of code total, near as minimal as I could make it.
https://github.com/ravenscar/ts29221
docker build -t ts29221 https://raw.githubusercontent.com/ravenscar/ts29221/master/Dockerfile
Expected behavior:
tsc
can compile the code
Actual behavior:
tsc
fails with the messge:
src/index.ts(3,14): error TS2742: The inferred type of ‘thingyTuple’ cannot be named without a reference to ‘…/…/b/node_modules/ts29221-a/dist’. This is likely not portable. A type annotation is necessary.
This used to work fine in TS < 3.2 and seems to have broken since then, looking at the breaking changes I can’t see anything mentioned which would apply here.
At first we thought we were experiencing issues due to symlinks created by lerna bootstrap and wanted to create an extremely minimal reproduction based on #29221, however we discovered that symlinks aren’t the problem and have produced a Dockerfile to show that:
docker build -t ts29221 https://raw.githubusercontent.com/ravenscar/ts29221/master/DockerfileNoSymlinks
We also have a Dockerfile showing this working in 3.1:
docker build -t ts29221 https://raw.githubusercontent.com/ravenscar/ts29221/master/Dockerfile31
What happens here is that module ts29221-a
exports a type definition, module ts29221-b
uses that type definition in the return type of a function, then module ts29221-c
uses that function to assign a value to a const.
This is using the new build system, with refs in the tsconfig, and is bootstrapped by lerna.
What is happening here is that when tsc
is compiling ts29221-c
it imports the types from ts29221-b
which in turn imports them from ts29221-a
. It then sees that they types are at a relative position of ../b/node_modules/ts29221-a/dist
compared to ts29221-c
’s package.json.
They are actually also at node_modules/ts29221-b/node_modules/ts29221-a/dist
relative to ts29221-c
’s package.json.
This is for example where they may be if they were installed by a package manager that didn’t flatten dependencies (e.g. npm2 or pnpm).
The only way around this is for c
to somehow know that b
uses a
and to add a
as a direct dependency of c
. Then to import the used type definition from a
before importing b
, even though c
doesn’t directly use a
(so will need a tslint:ignore too).
Or do something even worse such as put this workaround at the top of c/src/index.ts
:
import * as _ from '../node_modules/ts29221-b/node_modules/ts29221-a';
I think it is unreasonable for modules to need to know the dependencies of any of their submodules when it comes to types, although that is what we are currently doing in dozens of places.
I understand that the module resolution shows that it will only go up for node_modules but it really seems like something went wrong here as this pretty much makes TS broken for monorepos that use symlinks, if this is intentional then it’s hard to imagine how the new build/watch features (which are awesome BTW) are supposed to work when the ts module dependencies are multiple levels deep.
Related Issues: https://github.com/Microsoft/TypeScript/issues/29221 https://github.com/Microsoft/TypeScript/issues/29808 https://github.com/Microsoft/TypeScript/issues/26985
About this issue
- Original URL
- State: closed
- Created 5 years ago
- Reactions: 31
- Comments: 19 (6 by maintainers)
I see this in a monorepo that uses pnpm to link the packages together. Under Mac OS the project reports a few of these sorts of errors in files A and B, but if I build via a Ubuntu image in Docker then I see the error reported in files C and D. I can work around the issue by adding explicit types at each location mentioned in the errors.
So this was a weird one, but I’m going to close this as “fixed” by #33567. For anyone who simply has a symlinked monorepo where they never split up and publish the individual packages, this is a quality of life improvement. If, on the other hand, someone runs into a problem with non-portable paths that is now uncaught due to #33567, we’ll consider more sophisticated logic for catching that problem as a feature request, and at that point we’ll try to implement it without messing up the “my monorepo works fine, don’t make my life harder by giving me errors that aren’t relevant to me” scenario.
@weswigham I’ve come across this a few times, and I agree with this explanation that it’s not a bug. But I do think typescript could do something more helpful than throwing an error when it crops up.
A dead-simple solution I can think of is, instead of throwing an error, declare the un-nameable transitive dependency type as
unknown
. There could even be a comment saying where it would need to find the reference to be typed properly, if it’s possible to do that in declaration files.I’ve usually come up against this when I’m trying to export a utility function within an internal module of a library, but one that isn’t intended to re-exported by the library’s
main
module. Since the library defines its own domain, the main module typically has self-sufficient types without any dependencies. If thed.ts
file for the internal module has a fewunknown
s in it - I don’t care at all. I’m never using the declarations for that particular export, only the source directly.Another solution would be to allow
emitDeclaration
to take a whitelist/blacklist to avoid even trying to export declarations for certain modules, but that seems much more complex, and more overhead to maintain.I can’t speak for others, but I think falling back to
unknown
would solve all the cases I’ve seen this in without causing other issues. And logically it seems like a sensible action - given what you (rightly) said about getting the full dependency type:@weswigham
I get there is a workaround in your example for
A
to directly importC
. We have about 100 modules in our monorepo and this problem now comes up regularly and we have an increasing number ofimport * as ignoreThisTypescriptBug from 'C'
in a bunch of our “A
” modules.An practical example for us would be we have a module which works with the database
C1
, and one which creates types from a graphql schemaC2
. These are both pretty complicated and I generally don’t want to have and of myA1 ...A20
modules deal with it, or even know they exist. I have aB
module which has some complex typescript typings to convert between theseC1
andC2
and create a lovely simple facade for all of theA*
modules to use.Unfortunately because of this bug all the
A*
modules now have a bunch of direct imports and dependencies littered through them due to this bug.Now I really really really don’t think it is at all reasonable for A to need to know about C at all. I don’t know if the TS devs are considering this an actual bug and intend to fix it, or just a weird feature of the compiler which has a workaround for people using monorepos.
The reason why many people are not experiencing this is because they are using aftermarket typings from the
@types
package.Imagine if there were first party typings for all the database modules, then imagine that knex (quite legitimately) used these types in its own typings e.g. for the db connection setup. Is it reasonable to tell people that despite using knex to isolate themselves from low level database modules they still need to import them all over the place?
FYI I have also create a fork of the lerna example from @RyanCavanaugh but with the code for the above modules: https://github.com/ravenscar/learn-a
It fails with the same error: