Description
Error on Value/Import Alias Merging in isolatedModules
// @isolatedModules: true
import { writeFile } from "fs";
const writeFile = ...;
- Some compilers say that if an import exists with the same name as a module-local value binding, the import must be a type (and thus can be elided).
- Seems kind of questionable.
- isolatedModules error on alias merging with local value #56354 changes the behavior to error under
isolatedModules
. - It's a breaking change under
isolatedModules
(andverbatimModuleSyntax
), but is pretty easy to fix - add atype
modifier.import { type writeFile } from "fs";
- Not much seems to break.
- We vote yes, make the change for 5.4.
Preserving Tags on Primitives in Template String Types
type S1 = `xyz${ "hello" & { __someTag: void } }`;
-
TypeScript preserves these branded strings instead of just producing the string literal type
"xyzhello"
. -
History
- Original issue was Template string type infers error when string intersect with object type #48034, noted that using an intersection in a template literal converted it to "string"
- We merged fix(48034): Get a literal string of type intersection in a template literal type #48044, which did actually handle it, flattening
- Performance regression from #48044 #52345 noted a massive perf regression from this flattening
- We then merged Revert fix for intersections in template literals, fix differently #52836, which undid the other PR and fixed perf by deferring them / leaving them in
- User notices in 5.1.3 new "feature"? Branded types are preserved in
const
template literal #54648 and says "hey is intended?" - We then recalled Incorrect handling of template literals + intersections #53427 in review
-
Perf issues, poor display - why are we supporting this?
-
Incidental behavior that felt more correct from a perf fix.
-
But nobody asked for this feature...
- ...well, somebody did! They asked if it's something they could depend on when they discovered it.
-
Type display is orthogonal (plus you can add an alias which looks much better).
-
Tagged strings are taking a bit too much of a life of their own if we really support this. We would prefer not to embed more and more and more support.
-
You could imagine the use cases - a cache key that's written as a
`${TypeId}|TypeId`
. -
Part of this really has to do with optimization. Regardless of the direction, all these scenarios need to be done performantly.
-
"People don't really use tagged primitive types, we shouldn't support them here" is pretty inconsistent with lots of our design decisions! They are supported as a first-class citizen in index signatures, for example.
interface Yadda { [x: string]: number | string; [x: string & { __foo: void }]: string; } declare var x: Yadda; let a = x["hello"] // ^? let b = x["hello" as string & { __foo: void }] // ^?
-
Even if we had tagged types, what would we do differently here? You'd still end up with a perf bomb if we handled things correctly, right?
-
The examples like
type S1 = `xyz${ "hello" & { __someTag: void } }`;
are not realistic, it's stuff closer toFilePath
- places where you want a nominalstring
. -
But
FilePath
is not an inhabited type, you are sort of lying to the type system.
Allow Intersections As Valid Types for Template Literal Placeholders
- Libraries (like csstype) use
"abc" | "def" | string & {}
to describe an open-ended string union. Useful for tooling. - People ended up wanting to preserve these in literal types like
`prefix${"abc" | "def" | string & {}}`
, so Allow intersections to be used as valid types for template literal placeholders #54188 was opened up. - If you preserve the intersection, you can still get tooling benefits. If you don't, then the entire type will be subsumed by
`prefix${string}`
. - There is a way to manually preserve as
"prefixabc" | "prefixdef" | (`prefix${string}` & {})
. - We don't like this, but it's the best we have.
Hacks to Get (-)Infinity Types
type PositiveInfinity = 1e999;
type NegativeInfinity = -1e999;
type TypeofInfinity = typeof Infinity;
type TypeofNaN = typeof NaN;
- We have not permitted
Infinity
, but you can hack around it. - In 5.3,
PositiveInfinity
is represented asnumber
because1e999
is unrepresentable. - Missed a case for negation so in TypeScript 5.3, we do the wrong thing for
NegativeInfinity
and now say it's-1e999
which is-Infinity
. - Can we just support
Infinity
in the type system?- Well we don't have a
-
operator in the type system.-1
is a literal type, but-(1)
is not.
- Well we don't have a
- Are people actually using these
Infinity
types?- Do people want it?
- Allow Inifinity and -Infinity as number literal types #32277
Array.prototype.flat
- legit!function testSomething(n: 0 | 1 | Infinity): void
- this is a decent jokefunction sleepSync(timeout: Infinity): never
- this is a good joke
- Conclusion: Let's revert and try to work out how Infinity works in the type system.