Closed
Description
newLine
and forceConsistentFileNames
Defaults
newLine
tolf
?- Didn't get to discuss it thoroughly.
- Change default of
forceConsistentFileNames
totrue
?- Catch issues between Windows/Mac/Linux ahead of pushing out to CI.
- We think it's good to turn on.
- We all feel okay about the fact that it doesn't "do what people think", right?
- "What?"
- It doesn't check whether the paths match what's on disk, it just checks whether the paths are consistent across the project.
- Deprecate it?
- Prefer not to.
- Conclusion
--newLine lf
by default.--forceConsistentFileNames true
by default.
catch
Clause Variables
// @useUnknownInCatchVariables: false
try {} catch ({ e }) {}
// ^?
try {} catch ({ e }: any) {}
// ^?
try {} catch ({ e }: unknown) {}
// ^?
try {} catch ({ x: { e } }) {}
// ^?
try {} catch ({ x: { e } }: any) {}
// ^?
try {} catch ({ x: { e } }: unknown) {}
// ^?
- Toggle
useUnknownInCatchVariables
betweentrue
andfalse
. - When
true
, the annotations are ignored, and destructured declarations are permitted andunknown
. - Make catch clause checking consistent with variable declarations #52240 fix this.
- Breaking change, but only found buggy code under
useUnknownInCatchVariables
.- We think this is good for 5.0.
- Also: Allow arbitrary catch clause type annotations #52280 allows arbitrary type annotations on catch variables - addresses Allow type annotation on catch clause variable #20024.
- But we're not doing that, right?
- No, blatantly unsafe.
- Well then we should tell people that we're not doing it?
- Context matters - we think pattern matching progressing in TC39 is going to enable a safe version of this pattern.
- We want to defer to that.
- Conclusion
- We're not doing Allow arbitrary catch clause type annotations #52280.
- We're doing Make catch clause checking consistent with variable declarations #52240.
Comparison Operators
- Some obvious but questionable things that popped up.
Number < number
now disallowedstring | number < number
now disallowedunknown < number
now disallowed
- Some stuff that fails due to control flow limitations across functions.
- What about objects that coerce to
number
(e.g.Date
)?- Why don't we look at
valueOf
?- Or
[Symbol.toPrimitive]
? - Here we go...
- Or
- Why don't we look at
- Conclusion: Feels like this PR is a good start. Want to bring this in for 5.0.
- Watch for 5.0 Beta feedback.
JSON.stringify
- This thing broke some code.
- Technically
stringify
can returnundefined
.- But technically also wrong because a
Function
can have atoJSON
method.
- But technically also wrong because a
- There's a world where we could possibly keep this as-is if we reordered the signatures so that
never
works.- Overload resolution goes through a subtype pass.
- But why are we adding this overload? Who ends up in a situation like this?
- Doesn't even catch mistakes like
(() => string) | string
today. - Only contrived cases are caught.
- Doesn't even catch mistakes like
- The idea that everyone has to suffer for
stringify
because of these contrived-but-uncommon cases feels unreasonable. - Could ship an open-ended union?
- Could also just tell people to interface-merge.
- Conclusion:
- Revert this, tell people to interface-merge on
JSON
with something that returnsundefined
.
- Revert this, tell people to interface-merge on
Picking Between Types from Type Predicates and Original Types
- Made a change a while back to pick the types from type predicates/type assertions.
- Similar behavior with
instanceof
too. - Means that the original type does not survive at joining points of CFA.
- Idea
- In the true branch, we will still prefer the type predicate type.
- In the false branch, we will now preserve the original type so that they join back at a CFA merge node.
- So what do we now do about mutual subtypes?
- For example,
unknown
andany
are mutual subtypes. - Today we choose the type with the lowest ID.
- Pretty unfortunate - these have measurable effects.
- So we are making
any
a strict supertype ofunknown
.- Behavior directly in unions is the same.
- Where else does this appear?
-
When the type IDs are not compared directly.
-
So for e.g.
Array<any>
vs.Array<unknown>
- butArray
is not special.type Box<T> = { readonly value: T }; declare let anyBox: Box<any>; declare let unknownBox: Box<unknown>; let boxes1 = [anyBox, unknownBox]; // ^? let boxes2 = [unknownBox, anyBox]; // ^?
-
Now you always end up with
Box<any>[]
.
-
- Similar constraints with
{}
and any non-empty object type.
- For example,
- What other effects does this have?
-
The type of an assignment expression is always the right side:
function f(obj: { a?: string }) { (obj = {}).a; // allowed? }
-
Hmm...
function f(a: { x?: number }, b: { x?: string}) { a = b = {}; // allowed? // Equivalent to `a = (b = {})` which naively looks like `b = {}; a = {}` from a type perspective. a.x = 42; b.x?.toUpperCase(); }
- Fresh
{}
object type has "inverted" subtype behavior.- It is, but on the assignment we widen.
- Do we have to widen? Could choose not to.
- Feels like for the
b = {}
, we have to propagate out some information, or further narrow based on the left side(?)
- Fresh
-
(...args: any[]) => any
should be a super-type of all functions?- But what about
(...args: never[]) => unknown
?- Not specially understood today by TS internals.
- But what about
-