Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Narrow generic conditional and indexed access return types when checking return statements#56941

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
gabritto merged 102 commits intomainfromgabritto/d2-detect
Nov 6, 2024

Conversation

gabritto
Copy link
Member

@gabrittogabritto commentedJan 3, 2024
edited
Loading

Fixes#33912.
Fixes#33014.

Motivation

Sometimes we want to write functions whose return type is picked between different options, depending on the type of a parameter. For instance:

declareconstrecord:Record<string,string>;declareconstarray:string[];functiongetObject(group){if(group===undefined){returnrecord;}returnarray;}constarrayResult=getObject("group");constrecordResult=getObject(undefined);

If we want to precisely express this dependency between the return type and the type ofnameOrId, we have a few options.
The first one is to use overloads:

declareconstrecord:Record<string,string[]>;declareconstarray:string[];functiongetObject(group:undefined):Record<string,string[]>;functiongetObject(group:string):string[];functiongetObject(group:string|undefined):string[]|Record<string,string[]>;functiongetObject(group:string|undefined):string[]|Record<string,string[]>{if(group===undefined){returnrecord;}returnarray;}constarrayResult=getObject("group");constrecordResult=getObject(undefined);

However, if you make a mistake in the implementation of the function and return the wrong type, TypeScript will not warn you. For instance, if instead you implement the function like this:

declareconstrecord:Record<string,string[]>;declareconstarray:string[];functiongetObject(group:undefined):Record<string,string[]>;functiongetObject(group:string):string[];functiongetObject(group:string|undefined):string[]|Record<string,string[]>;functiongetObject(group:string|undefined):string[]|Record<string,string[]>{if(!group){// An empty string is falsyreturnrecord;}returnarray;}constbadResult=getObject("");// Type says this returns `string[]`, but actually it returns a record.

then your function implementation doesn't respect the overload signatures, but TypeScript will not error.

The alternative to overloads is to use conditional types, like so:

declareconstrecord:Record<string,string[]>;declareconstarray:string[];functiongetObject<Textendsstring|undefined>(group:T):Textendsstring ?string[] :Textendsundefined ?Record<string,string[]> :never{if(group===undefined){returnrecord;// Error! Type 'Record<string, string[]>' is not assignable to type 'T extends string ? string[] : T extends undefined ? Record<string, string[]> : never'.}returnarray;// Error! Type 'string[]' is not assignable to type 'T extends string ? string[] : T extends undefined ? Record<string, string[]> : never'}constarrayResult=getObject("group");constrecordResult=getObject(undefined);

However, while everything works out for the callers ofgetObject, in the implementation TypeScript errors on the return statements, because it compares the type of the return expression to the annotated conditional return type, andRecord<string, string[]> is not assignable toT extends undefined ? Record<string, string[]> : never.

Solution: conditional return type narrowing

For this PR, I propose a way of checking return statements that understands cases like above.
The idea is that, for the function above:

functiongetObject<Textendsstring|undefined>(group:T):Textendsstring ?string[] :Textendsundefined ?Record<string,string[]> :never{if(group===undefined){returnrecord;}returnarray;}

when checking the return statementreturn record, TS will know thatgroup has typeundefined. TS will also know that type parameterT corresponds exactly to the type ofgroup. Combining those two pieces of information, TS will know that, inside that branch, the expected return type has to beRecord<string, string[]> (or a supertype). Then, instead of checkingreturn record's type againstT extends string ? string[] : T extends undefined ? Record<string, string[]> : never, it will checkreturn record's type against theRecord<string, string[]> branch of the conditional, i.e. a narrowed version of the conditional type. Then there will be no error on the return statement. In the same manner, when checking the return statementreturn array, TS will know thatgroup has typestring, and that therefore it can check the type of the return statement against thestring[] branch of the conditional return type.

For now, we can think of it like this: when we check return statementreturn record, we see thatgroup has narrowed typeundefined. Then, we plug that information back into the return type by instantiatingT extends string ? string[] : T extends undefined ? Record<string, string[]> : never withT replaced byundefined.

Restrictions

Conditional types

Reasoning about conditional types is pretty tricky. In general, given a functionf(...args): SomeConditionalType whose return type is some (generic) conditional type, we are not able to do the special check proposed above, because it wouldn't be safe.
We need to place some restrictions on what the conditional return type looks like in order for TS to safely analyze it.

We can safely analyze a conditional return type that has the shapeT extends A ? AType : T extends B ? BType : never.
This means the conditional type needs to be distributive (i.e. its check type is a naked type parameterT) and havenever as its false-most type, and it cannot haveinfer type parameters (e.g. it cannot beT extends [infer A] ? AType : T extends B ? BType : never).

Intuitively, we can think of this conditional type shape as reflecting the kind of code one would write in the implementation of such a function.

In addition to the previous restrictions, the type parameter constraint has to be a union type, and the extends types of the conditional (A andB above) have to be constituents of the type parameter's union constraint (T above), like so:

functionfun<TextendsA|B>(param:T):TextendsA ?AType :TextendsB ?BType :never{if(isA(param)){ ...}else{ ...}}

This is because, to narrow the return type, we first need to narrow the type ofparam (more on that below). When we narrow the type ofparam, in a lot of scenarios, we will start from its type,T, or in this case its type constraint,A | B. Then, we will further narrow that type based on information from control flow analysis, e.g. to pick eitherA orB (seegetNarrowableTypeForReference inchecker.ts).

Therefore, in this typical case, narrowingparam means we will end up with a type that is eitherA,B, or a subtype of those. In turn, when we plug this narrowed type back into the conditional return type, this means we will be able to pick a branch of the conditional type and resolve it. e.g. if the narrowed type ofparam isA, the conditional type will resolve toAType.

These additional restrictions are a heuristic meant to capture the "happy path" of narrowing in TS. For instance, if the type parameter's constraint is not a union, then we might have a case like the below:

functionbad<Textendsunknown>(x:T):void{if(x!=undefined){consty:{}=x;}else{consty:null|undefined=x;// Error: `x` is not narrowed here}}functiongood<Textends{}|null|undefined>(x:T):void{if(x!=undefined){consty:{}=x;}else{consty:null|undefined=x;// Works: `x` is narrowed here}}

(Noteunknown is conceptually equivalent to{} | null | undefined, but writingTs constraint as a union instead ofunknown makes narrowing work.)

Aside: whynever

A common way of trying to write a conditional return type is like the following:

functionstringOrNumber<Textendsstring|number>(param:T):Textendsstring ?string :number{if(typeofparam==="string"){return"some string";}return123;}constnum=stringOrNumber(123);conststr=stringOrNumber("string");declareletstrOrNum:string|number;constboth=stringOrNumber(strOrNum);

This example works fine and it would be safe for TS to allow that function implementation. However, in general, it is not safe to allow this pattern of conditional return type. Consider this case:

functionaStringOrANumber<Textends{a:string}|{a:undefined}>(param:T):Textends{a:string} ?string :number{if(typeofparam.a==="string"){return"some string";}return123;}constaNum=aStringOrANumber({a:undefined});constaStr=aStringOrANumber({a:""});// Bad! Type says `number`, but actually should say `string | number`constaNotBoth=aStringOrANumber({a:strOrUndef});

The problem boils down to the fact that, when a conditional return type resolves to its false branch, we can't know if the check type is related or not to the extends type. For the example above, when we plug in{ a: undefined } forT inT extends { a: string } ? string : number, then we fall into the false branch of the conditional, which is desired because{ a: undefined } does not overlap{ a: string }. However, when we plug in{ a: string | undefined } forT inT extends { a: string } ? string : number, we fall into the false branch of the conditional, but this is not desired because{ a: string | undefined } overlaps{ a: string }, and therefore the return type could actually bestring.

Resolving a conditional type to its false-most branch of a conditional type doesn't provide TS with enough information to safely determine what the return type should be, and because of that, narrowing a conditional return type requires the false-most branch of the conditional to benever.

Type parameter references

As hinted at above, to narrow a conditional return type, we first need to narrow a parameter, and we need to know that the type of that parameter uniquely corresponds to a type parameter.
Revisiting our example:

functiongetObject<Textendsstring|undefined>(group:T):Textendsstring ?string[] :Textendsundefined ?Record<string,string[]> :never{if(group===undefined){returnrecord;}returnarray;}

To narrow the return typeT extends string ? string[] : T extends undefined ? Record<string, string[]> : never, we first narrow the type ofgroup inside theif branch, and then we can use that information to reason about what typeT could be replaced with. This only works because the declared type ofgroup is exactlyT, and also because there are no other parameters that use typeT. So in the following cases, TS would not be able to narrow the return type, because there is no unique parameter to whichT is linked:

functionbadGetObject1<Textendsstring|undefined>(group:T,someOtherParam:T):Textendsstring ?string[] :Textendsundefined ?Record<string,string[]> :never{    ...}functionbadGetObject2<Textendsstring|undefined>(group:T,options:{a:number,b:T}):Textendsstring ?string[] :Textendsundefined ?Record<string,string[]> :never{    ...}

Indexed access types

The reasoning explained above for conditional types applies in a similar manner to indexed access types that look like this:

interfaceF{"t":number,"f":boolean,}functiondepLikeFun<Textends"t"|"f">(str:T):F[T]{if(str==="t"){return1;}else{returntrue;}}depLikeFun("t");// has type numberdepLikeFun("f");// has type boolean

So cases like this, where the return type is an indexed access type with a type parameter index, are also supported by this PR. The thinking is similar: in the code above, when we are in theif (str === "t") { ... } branch, we knowstr has type"t", and we can plug that information back into the return typeF[T] which resolves to typenumber, and similarly for theelse branch.

Implementation

The implementation works roughly like this:
When checking a return statement expression:

  • We check if the type of the expression is assignable to the return type. If it is, good, nothing else needs to be done. This makes sure we don't introduce new errors to existing code.
  • If the type of the expression is not assignable to the return type, we might need to narrow the return type and check again. We proceed to attempt narrowing the return type.
  • We check if the return type has the right shape, i.e. it has a shape of eitherSomeType[T] orT extends A ? AType : T extends B ? BType : never, and what parameters the type parameters are uniquely linked to, among other requirements. If any of those requirements is not met, we don't continue with narrowing.
  • For every type parameter that is uniquely linked to a parameter, we obtain its narrowed type.
    • Say we have type parameterT that is uniquely linked to parameterparam. To narrow the return type, we first need to obtain the narrowed type forparam at the return statement position. Because, in the source code, there might not be an occurrence ofparam at the return statement position, we create a synthetic reference toparam at that position and obtain its narrowed type via regular control-flow analysis. We then obtain a narrowed typeN forparam. (If we don't, we'll just ignore that type parameter).
  • Once we have the narrowed type for each type parameter, we need to plug that information back into the return type. We do this by instantiating the return type with a substitution type. For instance, if the return type isT extends A ? AType : T extends B ? BType : never,T is linked toparam, andparam has narrowed typeN, we will instantiate the return type withT replaced byT & N (as a substitution type).
  • Once we obtain this narrowed return type, we get the type of the return expression, this time contextually checked by the narrowed return type, and then check if this type is assignable to the narrowed return type.

Instantiation

As mentioned above, the process of narrowing a return type is implemented as instantiating that return type with the narrowed type parameters replaced by substitution types. Substitution types can be thought of asT & A, whereT is the base type andA the constraint type. There are a few changes made to instantiation to make this work:

  • There is now a new kind of substitution type, indicated by theObjectFlags.IsNarrowingType flag, which corresponds to substitution types created by return type narrowing.
  • These narrowing substitution types are handled in a special way by conditional type instantiation functions, i.e.getConditionalTypeInstantiation, andgetConditionalType.
  • getConditionalTypeInstantiation is responsible for distributing a conditional type over its check type. When instantiating a distributive conditional type ingetConditionalTypeInstantiation, if the conditional's check type is a substitution type likeT & (A | B), the usual logic would not distribute over this type, because it's a substitution type and not a union type. So, for distribution to happen, we have to take apart theT & (A | B) into(T & A) | (T & B), and distribute over that.
  • The other special thing that we have to do ingetConditionalTypeInstantiation is to take the intersection of the distribution result, as opposed to the union. This is because, if we narrow a type parameterT toA | B, and we have a conditional return typeT extends A ? R1 : T extends B ? R2 : T extends C ? R3 : never, then we don't know which branch of the conditional return to pick, if branchT extends A ? R1, or branchT extends B ? R2, so we have to check whether the return expression's type is assignable to both, i.e. assignable toR1 & R2.
  • Validating whether the conditional type has the right shape to be narrowed happens on-demand ingetConditionalTypeInstantiation (and also at first as an optimization incheckReturnExpression when we're deciding whether to narrow the return type). This is because, as we instantiate a type, we may produce new conditional types that we need to then decide are safe to narrow or not (seenested types case independentReturnType6.ts test).

Conditional expression checking

To support conditional expression checking in return statements, this PR changes how we check a conditional expression in a return statement. Before this PR, when checking a return statementreturn cond ? exp1 : exp2, we obtain the type of the whole expressioncond ? exp1 : exp2, and then compare that type to the return type. With this PR, we now separately check each branch: we first obtain the type ofexp1, and compare that type to the return type, then obtain the type ofexp2 and compare that type to the return type. This allows us to properly check a conditional expression when return type narrowing is needed.

This a breaking change, and the only change that affects existing code, but this change finds bugs.Analysis of extended tests changes:#56941 (comment).
This change also slightly affects performance because we do more checks.Latest perf results here:#56941 (comment).

Performance results

This feature is opt-in. Currently, virtually no code has functions whose return types are conditional or indexed access types that satisfy the restrictions above, so no code goes down the new code path for narrowing return types. This means for existing code, there's no performance impact from the return type narrowing. The only current performance impact is from the conditional expression checking change (see a version of this PR without the change for conditional expressions:#60268 (comment)).

Assessing the performance of the new code path is tricky, as there are no baselines. The existing alternative to conditional return types is to use overloads. In one scenario I tested, checking a function written with conditional return types with this PR took ~+16% check time compared to the same function using overloads and the main branch. However, that's for checking the function declaration. In a different scenario where I included a lot offunction calls though, the version with conditional return types + this PR took ~-15% compared to overloads + main branch. So I'd say the performance is acceptable, especially considering you get stronger checks when using conditional return types, and also only a small number of functions in a codebase should be written using this feature.

Unsupported things

  • Inference:
    TS will not infer a conditional return type or an indexed access type for any function or expression. Inferring such a type is more complicated than checking, and inferring such a type could also be surprising for users. This is out of scope.

  • Contextually-typed anonymous functions:

typeGetObjectCallback=<Textendsstring|undefined>(group:T)=>Textendsstring ?string[] :Textendsundefined ?Record<string,string[]> :never;constgetObjectBad1:GetObjectCallback=(group)=>{returngroup===undefined ?record :array};// ErrordeclarefunctionouterFun(callback:GetObjectCallback);outerFun((group)=>{returngroup===undefined ?record :array});// Error

This is because, if your function does not have an explicitly annotated return type, we will infer one from the returns.

  • Detection of link between parameter and type parameter in more complicated scenarios:
// All cases below are not recognized// Type aliastypeId<X>=X;functionf2<Textendsboolean>(arg:Id<T>):Textendstrue ?string :Textendsfalse ?number :never{if(arg){return"someString";}return123;}// Property typefunctionf3<Textendsboolean>(arg:{prop:T}):Textendstrue ?string :Textendsfalse ?number :never{if(arg.prop){return"someString";}return123;}// Destructuringfunctionf4<Textendsboolean>({ arg}:{arg:T}):Textendstrue ?string :Textendsfalse ?number :never{if(arg.prop){return"someString";}return123;}// Combinations of the above, e.g.:typeOpts<X>={prop:X};functionf5<Textendsboolean>(arg:Opts<T>):Textendstrue ?string :Textendsfalse ?number :never{if(arg.prop){return"someString";}return123;}

This could be supported in the future.

btoo, yongsk0066, cuikho210, pavelsvitek, johnsoncodehk, zyhou, edunomatseye, zirkelc, pawk3k, anurag-roy, and 74 more reacted with thumbs up emojibtoo, yongsk0066, jethrolarson, amitbeck, andriyor, timbrinded, edunomatseye, pawk3k, sirreal, anurag-roy, and 50 more reacted with hooray emojiandrewbranch, Andarist, btoo, yongsk0066, fpapado, macmillen, tillsanders, jethrolarson, johnsoncodehk, diginikkari, and 63 more reacted with heart emojimalthe, dimitropoulos, Kirens, macmillen, andrewbranch, Andarist, btoo, sebastiandotdev, mbelsky, jethrolarson, and 44 more reacted with rocket emojidanvk, amitbeck, tefkah, DuCanhGH, anuraghazra, Nsttt, acommodari, EdamAme-x, motss, mkrause, and 8 more reacted with eyes emoji
@typescript-bottypescript-bot added Author: Team For Uncommitted BugPR for untriaged, rejected, closed or missing bug labelsJan 3, 2024
@gabritto
Copy link
MemberAuthor

@typescript-bot perf test this

@typescript-bot
Copy link
Collaborator

typescript-bot commentedJan 3, 2024
edited
Loading

Heya@gabritto, I've started to run the regular perf test suite on this PR ata3a54ce. You can monitor the buildhere.

Update:The results are in!

@typescript-bot

This comment was marked as outdated.

@gabritto
Copy link
MemberAuthor

@typescript-bot perf test this

@typescript-bot
Copy link
Collaborator

typescript-bot commentedJan 3, 2024
edited
Loading

Heya@gabritto, I've started to run the regular perf test suite on this PR at1b7489f. You can monitor the buildhere.

Update:The results are in!

@typescript-bot
Copy link
Collaborator

typescript-bot commentedOct 31, 2024
edited
Loading

Starting jobs; this comment will be updated as builds start and complete.

CommandStatusResults
test top400✅ Started👀 Results
user test this✅ Started👀 Results
run dt✅ Started✅ Results
perf test this faster✅ Started👀 Results

@typescript-bot
Copy link
Collaborator

@gabritto Here are the results of running the user tests with tsc comparingmain andrefs/pull/56941/merge:

Something interesting changed - please have a look.

Details

adonis-framework

/mnt/ts_downloads/_/m/adonis-framework/tsconfig.json

  • [NEW]error TS2322: Type 'null' is not assignable to type 'string'.
    • /mnt/ts_downloads/_/m/adonis-framework/node_modules/adonis-framework/src/Request/index.js(600,75)

puppeteer

packages/puppeteer-core/tsconfig.json

@typescript-bot
Copy link
Collaborator

Hey@gabritto, the results of running the DT tests are ready.

Everything looks the same!

You can check the log here.

@typescript-bot
Copy link
Collaborator

@gabritto
The results of the perf run you requested are in!

Here they are:

tsc

Comparison Report - baseline..pr
MetricbaselineprDeltaBestWorstp-value
Compiler-Unions - node (v18.15.0, x64)
Errors3134🔻+3 (+ 9.68%)~~p=0.001 n=6
Symbols62,34062,363+23 (+ 0.04%)~~p=0.001 n=6
Types50,37950,395+16 (+ 0.03%)~~p=0.001 n=6
Memory used192,901k (± 0.09%)195,406k (± 0.96%)+2,505k (+ 1.30%)192,975k196,677kp=0.031 n=6
Parse Time1.31s (± 0.92%)1.31s (± 0.84%)~1.29s1.32sp=0.432 n=6
Bind Time0.72s0.72s~~~p=1.000 n=6
Check Time9.72s (± 0.56%)9.78s (± 0.30%)~9.75s9.82sp=0.087 n=6
Emit Time2.71s (± 1.45%)2.74s (± 0.48%)~2.72s2.76sp=0.141 n=6
Total Time14.47s (± 0.62%)14.55s (± 0.23%)~14.52s14.60sp=0.090 n=6
angular-1 - node (v18.15.0, x64)
Errors3337🔻+4 (+12.12%)~~p=0.001 n=6
Symbols947,886947,934+48 (+ 0.01%)~~p=0.001 n=6
Types410,840410,955+115 (+ 0.03%)~~p=0.001 n=6
Memory used1,224,607k (± 0.00%)1,226,060k (± 0.00%)+1,453k (+ 0.12%)1,226,012k1,226,117kp=0.005 n=6
Parse Time6.65s (± 0.85%)6.66s (± 0.71%)~6.60s6.72sp=0.809 n=6
Bind Time1.88s (± 0.27%)1.89s (± 0.22%)+0.01s (+ 0.44%)1.89s1.90sp=0.022 n=6
Check Time31.90s (± 0.60%)31.97s (± 0.36%)~31.81s32.13sp=0.471 n=6
Emit Time15.24s (± 0.31%)15.18s (± 0.41%)~15.07s15.25sp=0.078 n=6
Total Time55.68s (± 0.36%)55.69s (± 0.36%)~55.45s55.90sp=0.936 n=6
mui-docs - node (v18.15.0, x64)
Errors00~~~p=1.000 n=6
Symbols2,495,0262,495,029+3 (+ 0.00%)~~p=0.001 n=6
Types908,645908,650+5 (+ 0.00%)~~p=0.001 n=6
Memory used2,307,202k (± 0.00%)2,313,900k (± 0.00%)+6,697k (+ 0.29%)2,313,879k2,313,936kp=0.005 n=6
Parse Time9.33s (± 0.40%)9.33s (± 0.22%)~9.29s9.35sp=1.000 n=6
Bind Time2.14s (± 0.19%)2.14s (± 0.35%)~2.13s2.15sp=1.000 n=6
Check Time75.29s (± 0.51%)74.90s (± 0.36%)~74.41s75.13sp=0.173 n=6
Emit Time0.28s0.29s (± 2.58%)🔻+0.01s (+ 4.17%)0.28s0.30sp=0.009 n=6
Total Time87.04s (± 0.47%)86.66s (± 0.32%)~86.16s86.92sp=0.173 n=6
self-build-src - node (v18.15.0, x64)
Errors00~~~p=1.000 n=6
Symbols1,258,0871,258,781+694 (+ 0.06%)~~p=0.001 n=6
Types266,235266,471+236 (+ 0.09%)~~p=0.001 n=6
Memory used2,422,835k (± 0.01%)2,425,817k (± 0.01%)+2,983k (+ 0.12%)2,425,688k2,426,174kp=0.005 n=6
Parse Time5.21s (± 1.05%)5.20s (± 0.53%)~5.17s5.25sp=0.748 n=6
Bind Time1.92s (± 0.47%)1.95s (± 0.39%)+0.03s (+ 1.48%)1.94s1.96sp=0.004 n=6
Check Time35.50s (± 0.35%)35.59s (± 0.16%)~35.51s35.65sp=0.298 n=6
Emit Time3.04s (± 0.65%)3.02s (± 1.22%)~2.96s3.06sp=0.573 n=6
Total Time45.69s (± 0.31%)45.74s (± 0.17%)~45.65s45.84sp=0.575 n=6
self-build-src-public-api - node (v18.15.0, x64)
Errors00~~~p=1.000 n=6
Symbols1,258,0871,258,781+694 (+ 0.06%)~~p=0.001 n=6
Types266,235266,471+236 (+ 0.09%)~~p=0.001 n=6
Memory used2,619,643k (±11.18%)2,622,085k (±11.19%)+2,442k (+ 0.09%)2,500,959k3,221,358kp=0.045 n=6
Parse Time6.61s (± 2.23%)6.59s (± 1.84%)~6.52s6.83sp=0.376 n=6
Bind Time2.16s (± 1.98%)2.20s (± 4.21%)~2.10s2.37sp=0.423 n=6
Check Time43.37s (± 0.45%)43.39s (± 0.36%)~43.24s43.67sp=0.810 n=6
Emit Time3.61s (± 3.02%)3.52s (± 3.65%)~3.43s3.77sp=0.149 n=6
Total Time55.77s (± 0.61%)55.72s (± 0.47%)~55.53s56.20sp=0.936 n=6
self-compiler - node (v18.15.0, x64)
Errors00~~~p=1.000 n=6
Symbols261,754262,180+426 (+ 0.16%)~~p=0.001 n=6
Types106,477106,602+125 (+ 0.12%)~~p=0.001 n=6
Memory used438,797k (± 0.02%)439,725k (± 0.01%)+929k (+ 0.21%)439,660k439,785kp=0.005 n=6
Parse Time3.53s (± 1.15%)3.54s (± 0.39%)~3.52s3.56sp=0.808 n=6
Bind Time1.32s (± 0.48%)1.31s (± 0.79%)-0.01s (- 1.01%)1.29s1.32sp=0.028 n=6
Check Time18.90s (± 0.40%)18.92s (± 0.17%)~18.89s18.97sp=1.000 n=6
Emit Time1.54s (± 1.68%)1.51s (± 1.17%)~1.49s1.54sp=0.099 n=6
Total Time25.29s (± 0.32%)25.27s (± 0.16%)~25.23s25.34sp=0.520 n=6
ts-pre-modules - node (v18.15.0, x64)
Errors6870+2 (+ 2.94%)~~p=0.001 n=6
Symbols225,919226,060+141 (+ 0.06%)~~p=0.001 n=6
Types94,41594,488+73 (+ 0.08%)~~p=0.001 n=6
Memory used371,091k (± 0.01%)371,593k (± 0.01%)+502k (+ 0.14%)371,534k371,643kp=0.005 n=6
Parse Time2.90s (± 0.42%)2.90s (± 1.39%)~2.86s2.96sp=0.560 n=6
Bind Time1.58s (± 1.18%)1.57s (± 0.77%)~1.56s1.59sp=0.742 n=6
Check Time16.36s (± 0.34%)16.48s (± 0.28%)+0.12s (+ 0.73%)16.41s16.55sp=0.008 n=6
Emit Time0.00s0.00s~~~p=1.000 n=6
Total Time20.83s (± 0.31%)20.95s (± 0.40%)+0.12s (+ 0.57%)20.85s21.09sp=0.020 n=6
vscode - node (v18.15.0, x64)
Errors310🔻+7 (+233.33%)~~p=0.001 n=6
Symbols3,129,9283,138,510+8,582 (+ 0.27%)~~p=0.001 n=6
Types1,078,9471,082,406+3,459 (+ 0.32%)~~p=0.001 n=6
Memory used3,219,514k (± 0.03%)3,228,901k (± 0.00%)+9,388k (+ 0.29%)3,228,704k3,229,028kp=0.005 n=6
Parse Time14.07s (± 0.62%)14.04s (± 0.35%)~13.95s14.09sp=0.378 n=6
Bind Time4.84s (±14.62%)4.57s (± 2.98%)~4.47s4.78sp=0.936 n=6
Check Time86.30s (± 4.17%)85.18s (± 0.46%)~84.77s85.73sp=0.575 n=6
Emit Time26.78s (± 2.23%)27.95s (± 2.06%)🔻+1.17s (+ 4.38%)27.33s28.69sp=0.005 n=6
Total Time131.99s (± 2.65%)131.74s (± 0.58%)~130.83s132.83sp=0.471 n=6
webpack - node (v18.15.0, x64)
Errors00~~~p=1.000 n=6
Symbols287,028287,028~~~p=1.000 n=6
Types116,316116,316~~~p=1.000 n=6
Memory used438,405k (± 0.02%)438,641k (± 0.02%)+236k (+ 0.05%)438,550k438,767kp=0.008 n=6
Parse Time4.05s (± 0.88%)4.06s (± 0.96%)~4.01s4.12sp=1.000 n=6
Bind Time1.73s (± 1.57%)1.75s (± 0.98%)~1.73s1.78sp=0.123 n=6
Check Time18.54s (± 0.26%)18.51s (± 0.57%)~18.41s18.66sp=0.574 n=6
Emit Time0.00s0.00s~~~p=1.000 n=6
Total Time24.33s (± 0.36%)24.32s (± 0.45%)~24.17s24.46sp=0.936 n=6
xstate-main - node (v18.15.0, x64)
Errors34🔻+1 (+33.33%)~~p=0.001 n=6
Symbols543,130543,130~~~p=1.000 n=6
Types181,889181,891+2 (+ 0.00%)~~p=0.001 n=6
Memory used485,472k (± 0.02%)486,526k (± 0.01%)+1,054k (+ 0.22%)486,450k486,656kp=0.005 n=6
Parse Time4.19s (± 0.38%)4.19s (± 0.54%)~4.16s4.21sp=0.807 n=6
Bind Time1.46s (± 0.72%)1.47s (± 0.93%)~1.45s1.49sp=0.160 n=6
Check Time23.82s (± 1.20%)23.83s (± 0.29%)~23.77s23.95sp=0.173 n=6
Emit Time0.00s0.00s~~~p=1.000 n=6
Total Time29.47s (± 1.03%)29.48s (± 0.27%)~29.41s29.62sp=0.228 n=6
System info unknown
Hosts
  • node (v18.15.0, x64)
Scenarios
  • Compiler-Unions - node (v18.15.0, x64)
  • angular-1 - node (v18.15.0, x64)
  • mui-docs - node (v18.15.0, x64)
  • self-build-src - node (v18.15.0, x64)
  • self-build-src-public-api - node (v18.15.0, x64)
  • self-compiler - node (v18.15.0, x64)
  • ts-pre-modules - node (v18.15.0, x64)
  • vscode - node (v18.15.0, x64)
  • webpack - node (v18.15.0, x64)
  • xstate-main - node (v18.15.0, x64)
BenchmarkNameIterations
Currentpr6
Baselinebaseline6

Developer Information:

Download Benchmarks

@typescript-bot
Copy link
Collaborator

@gabritto Here are the results of running the top 400 repos with tsc comparingmain andrefs/pull/56941/merge:

Something interesting changed - please have a look.

Details

alibaba/formily

29 of 38 projects failed to build with the old tsc and were ignored

packages/grid/tsconfig.json

packages/grid/tsconfig.build.json

antvis/G2

2 of 3 projects failed to build with the old tsc and were ignored

tsconfig.json

cheeriojs/cheerio

2 of 3 projects failed to build with the old tsc and were ignored

tsconfig.typedoc.json

  • error TS2322: Type 'boolean' is not assignable to type 'string | number | Document | Element | CDATA | Text | Comment | ProcessingInstruction | ChildNode[] | ... 6 more ... | undefined'.

GrapesJS/grapesjs

1 of 3 projects failed to build with the old tsc and were ignored

packages/core/tsconfig.json

honojs/hono

7 of 8 projects failed to build with the old tsc and were ignored

tsconfig.json

ionic-team/stencil

39 of 42 projects failed to build with the old tsc and were ignored

scripts/tsconfig.json

jupyterlab/jupyterlab

46 of 59 projects failed to build with the old tsc and were ignored

examples/filebrowser/src/tsconfig.json

examples/console/src/tsconfig.json

microsoft/vscode

5 of 53 projects failed to build with the old tsc and were ignored

src/tsconfig.tsec.json

src/tsconfig.monaco.json

nextauthjs/next-auth

22 of 44 projects failed to build with the old tsc and were ignored

packages/adapter-pg/tsconfig.json

tailwindlabs/headlessui

2 of 5 projects failed to build with the old tsc and were ignored

packages/@headlessui-vue/tsconfig.json

vuejs/devtools-v6

7 of 8 projects failed to build with the old tsc and were ignored

packages/api/tsconfig.json

vuejs/vue

7 of 8 projects failed to build with the old tsc and were ignored

tsconfig.json

@gabrittogabritto added this to theTypeScript 5.8.0 milestoneNov 1, 2024
@gabrittogabritto merged commit30979c2 intomainNov 6, 2024
32 checks passed
@gabrittogabritto deleted the gabritto/d2-detect branchNovember 6, 2024 02:18
@whalderman
Copy link

I've been waiting for this glorious feature, thank you@gabritto !

@shicksshicks mentioned this pull requestFeb 4, 2025
1 task
@dgreensp
Copy link

Pardon my ignorance but, this PR is merged, but the behavior doesn't seem to be there in the playground in 5.8.1-rc or Nightly, e.g. using this example from above:

functiongetObject<Textendsstring|undefined>(group:T):Textendsstring ?string[] :Textendsundefined ?Record<string,string[]> :never{if(group===undefined){return{};// error}return[];// error}

What release is this feature coming in? Thanks.

@gabritto
Copy link
MemberAuthor

gabritto commentedFeb 28, 2025
edited
Loading

Pardon my ignorance but, this PR is merged, but the behavior doesn't seem to be there in the playground in 5.8.1-rc or Nightly, e.g. using this example from above:

functiongetObject<Textendsstring|undefined>(group:T):Textendsstring ?string[] :Textendsundefined ?Record<string,string[]> :never{if(group===undefined){return{};// error}return[];// error}

What release is this feature coming in? Thanks.

The feature got pulled from 5.8 because we needed to fix some aspects of its design. See#61136. The current plan is to implement the necessary fix and re-add this feature on 5.9.

dgreensp, dpolugic, Snowflyt, Jaakkonen, tefkah, Rucellmai61, and mateja176 reacted with thumbs up emoji

Copy link

@king0920king0306king0920king0306 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Ok

forivall and Rucellmai61 reacted with thumbs down emoji
Copy link

@craigwllcecraigwllce left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

finally

@craigwllcecraigwllce mentioned this pull requestJun 12, 2025
@mokhtarmoyy
Copy link

declare const record: Record<string, string>;
declare const array: string[];

function getObject(group) {
if (group === undefined) {
return record;
}
return array;
}

const arrayResult = getObject("group");
const recordResult = getObject(undefined);

Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Reviewers

@weswighamweswighamweswigham left review comments

@AndaristAndaristAndarist left review comments

@king0920king0306king0920king0306king0920king0306 left review comments

@craigwllcecraigwllcecraigwllce left review comments

@sandersnsandersnsandersn approved these changes

@andrewbranchandrewbranchandrewbranch approved these changes

@DanielRosenwasserDanielRosenwasserAwaiting requested review from DanielRosenwasser

Assignees

@gabrittogabritto

Labels
Author: TeamFor Uncommitted BugPR for untriaged, rejected, closed or missing bug
Projects
None yet
12 participants
@gabritto@typescript-bot@jakebailey@Andarist@whalderman@dgreensp@mokhtarmoyy@sandersn@weswigham@andrewbranch@king0920king0306@craigwllce

[8]ページ先頭

©2009-2025 Movatter.jp