You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Type and module imports of the same name shouldn't fail with "duplicate identifier"Β #48764
This is the intended behavior; import type still brings along the value meaning (if there is one), as you can demonstrate by writing something like
exportclassFoo{}
importtype{Foo}from"./other";// Legal to use value meaning of Foo (in type contexts)functionfn(x: typeofFoo){}
You're allowed to "shadow" import type with a const, but we don't allow you to have potentially-conflicting imports since there's no clear scoping rule to apply to that scenario.
Would it be possible to check what Foo is before bailing? In my use case Foo has no value reference so if I'm following correctly it should be unambiguous.
Specifically my use case is to import a module that represents a type and its operators as both a namespace (* as) and a type. This is a very common pattern in fp-ts, where you might import something like:
import*asTaskfrom'fp-ts/Task'
Ideally per this issue we could do:
importtype{Task}from'fp-ts/Task'
Right now at best you can redefine the type:
typeTask<A>=Task.Task<A>
Or worse yet carry the redundant namespace around with you everywhere:
In my use case Foo has no value reference so if I'm following correctly it should be unambiguous.
We don't want to create a forward-versioning hazard in the referenced module. In other words, adding a value meaning to an identifier in your module should not possibly break people who refer to that module.
Ryan Cavanaugh (@RyanCavanaugh) can you elaborate how adding a value meaning later could break existing code? is that only the case becauseimport type implicitly brings along the value meaning? additionally i'd like to understand why it does that, at first glance that sounds counter-intuitive to me.
the reason i'm asking: i was looking for a solution to exactly the same problem Sam Hartsfield (@samh) describes. right now i have two separate imports for half a dozen modules in almost every file, and it's starting to annoy me so much that i earnestly consider to rename all my types to T so i can at least do something like this:
so if import type did not implicitly bring along the value Foo everything would be fine?
i'm trying to see how this behaviour is useful (apart from the fact that existing code of course relies on it).
Seconding ritschwumm I've never seen import type used for anything other than importing actual type/interface declarations, the behaviour is surprising to me.
it seems the meaning of import type <name> is not - as we both assumed - "import the named type", but "import everything about things with this name that does not actually make it necessary to provide access to the values of this name".
Ryan Cavanaugh (@RyanCavanaugh) I'm unsure what to do regarding the closure of this ticket because this is still a pain point regardless of how it could potentially be solved. Should I raise a new ticket?
However, this is ugly and also has some significant differences.
(1) You now cannot import a single function, and must import the entire object. I guess this could be fixed by exporting and function and an object that contains the function, as below:
// You cannot do this:import(Point,add}from"./Point";// Unless you export twice:exporttypePoint={x: number,y: number};exportadd=(a: Point,b: Point): Point=>({x: a.x+b.x,x: a.y+b.y});exportconstPoint={ add };
(2) You get the error 'Point' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. when you reference Point in an initializer.
(3) It can also cause problems when attempting to tree-shake the module. However, in fairness, most JavaScript optimizers are now able to identify and hoist the exported object.
Here is my ideal syntax... import * as User, type { User } from "./User";
Ryan Cavanaugh (@RyanCavanaugh) I don't think this should have been closed, even if it is "working as intended."
This issue has been creating friction for developers trying to write functional code for years now.
I've listed a bunch of relevant discussions in this related issue: #51109
It's bizarre that type/namespace/function merging is supported within a file, but not via imports and exports.
Reacted by Ricardo Medina, Patrick Roza, jbress, Federico Biccheddu, matil019 and Loud Sven
Activity
RyanCavanaugh commentedon Apr 19, 2022
This is the intended behavior;
import typestill brings along the value meaning (if there is one), as you can demonstrate by writing something likeYou're allowed to "shadow"
import typewith aconst, but we don't allow you to have potentially-conflictingimports since there's no clear scoping rule to apply to that scenario.samhh commentedon Apr 19, 2022
I didn't know that about
import type!Would it be possible to check what
Foois before bailing? In my use caseFoohas no value reference so if I'm following correctly it should be unambiguous.Specifically my use case is to import a module that represents a type and its operators as both a namespace (
* as) and a type. This is a very common pattern in fp-ts, where you might import something like:Ideally per this issue we could do:
Right now at best you can redefine the type:
Or worse yet carry the redundant namespace around with you everywhere:
RyanCavanaugh commentedon Apr 19, 2022
We don't want to create a forward-versioning hazard in the referenced module. In other words, adding a value meaning to an identifier in your module should not possibly break people who refer to that module.
ritschwumm commentedon Apr 19, 2022
Ryan Cavanaugh (@RyanCavanaugh) can you elaborate how adding a value meaning later could break existing code? is that only the case because
import typeimplicitly brings along the value meaning? additionally i'd like to understand why it does that, at first glance that sounds counter-intuitive to me.the reason i'm asking: i was looking for a solution to exactly the same problem Sam Hartsfield (@samh) describes. right now i have two separate imports for half a dozen modules in almost every file, and it's starting to annoy me so much that i earnestly consider to rename all my types to
Tso i can at least do something like this:instead of
RyanCavanaugh commentedon Apr 19, 2022
Sure, let's say you write
This is the thing that you want to be OK. But if we did, then if you updated other.ts
Now
Foois ambiguously the class value or theimport *value and there's a new error inmyFile.tsritschwumm commentedon Apr 20, 2022
so if
import typedid not implicitly bring along the valueFooeverything would be fine?i'm trying to see how this behaviour is useful (apart from the fact that existing code of course relies on it).
samhh commentedon Apr 20, 2022
Seconding ritschwumm I've never seen
import typeused for anything other than importing actualtype/interfacedeclarations, the behaviour is surprising to me.typescript-bot commentedon Apr 22, 2022
This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.
ritschwumm commentedon Apr 23, 2022
it seems the meaning of
import type <name>is not - as we both assumed - "import the named type", but "import everything about things with this name that does not actually make it necessary to provide access to the values of this name".samhh commentedon Apr 23, 2022
Ryan Cavanaugh (@RyanCavanaugh) I'm unsure what to do regarding the closure of this ticket because this is still a pain point regardless of how it could potentially be solved. Should I raise a new ticket?
scottwillmoore commentedon Apr 26, 2022
I agree, it would be great if there was a good solution to this problem. You cannot do this:
The only workaround that I have been able to find is this:
However, this is ugly and also has some significant differences.
(1) You now cannot import a single function, and must import the entire object. I guess this could be fixed by exporting and function and an object that contains the function, as below:
(2) You get the error
'Point' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.when you reference Point in an initializer.(3) It can also cause problems when attempting to tree-shake the module. However, in fairness, most JavaScript optimizers are now able to identify and hoist the exported object.
TLDR
This pattern is very common in FP oriented libraries (gcanti/fp-ts#1003, gcanti/fp-ts#1044, gcanti/fp-ts#1415, gcanti/fp-ts#1633).
cruhl commentedon Oct 25, 2022
Sam A. Horvath-Hunt (@samhh) Scott Moore (@scottwillmoore) Please help support the latest issue on this before it gets closed again π
#51109
cruhl commentedon Oct 25, 2022
Here is my ideal syntax...
import * as User, type { User } from "./User";Ryan Cavanaugh (@RyanCavanaugh) I don't think this should have been closed, even if it is "working as intended."
This issue has been creating friction for developers trying to write functional code for years now.
I've listed a bunch of relevant discussions in this related issue: #51109
It's bizarre that type/namespace/function merging is supported within a file, but not via imports and exports.