Repository navigation
checker: read a type's declared members while its base types resolve - #21
RedesignedRobot wants to merge 1 commit into
Conversation
|
Note Currently processing new changes in this PR. This may take a few minutes, please wait... ⚙️ Run configuration
📒 Files selected for processing (3)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
Thank you for this, @RedesignedRobot. The analysis of the cycle in Our rule is that tsc-rs output stays byte-equal to The best place for this change is upstream, on microsoft/TypeScript#64605: your reduction and the two tests would help there. When upstream merges a fix, tsc-rs gets it with the next pin bump, and #20 tracks that. Thanks again. |
Fixes #20.
With Zod 4.5.0 to 4.6.5, any
z.inferof an object holdingz.json()reports TS5115 ($ZodTypeInternals,$InferZodRecordInput,IsOptionalIn). tsc 7.0.2 checks it clean. The pinned Go has the same error, from microsoft/TypeScript#64372.Cause
microsoft/TypeScript#64372 stopped
resolveObjectTypeMembersfrom storing declared members before the base types resolve, because the partial list caused ghost errors (microsoft/TypeScript#62180). Now a reference back to the type from a base type argument starts member resolution over, which instantiates the same bases again until the depth limit.Zod hits that loop.
$ZodRecordInternals<ZodString, ZodJSONSchema>has$InferZodRecordInputin its base type arguments, and that readsoptinthroughIsOptionalInover the union members, which leads back to$ZodRecordInternals<ZodString, ZodJSONSchema>itself. But that type declaresoptin, andaddInheritedMembersnever replaces a declared value member, so the answer is known before the bases resolve. The cycle isn't real. The reduced example in microsoft/TypeScript#64605 reaches the same point throughisStringIndexSignatureOnlyType.Change
While
resolve_object_type_membersadds inherited members, it keeps the type's declared members on a small stack. During that window:get_property_of_type_exreturns a value member the type declares itself, which is the same symbol the resolved members will hold.is_string_index_signature_only_type_workerreturns false for a type that declares a property.Every other query resolves the members again, as microsoft/TypeScript#64372 does, and nothing partial is stored on the type. Two unit tests in
checker_p21.rscover the Zod reduction plus the #64605 example, and the #64372noGhostErrorsandkeyofGenericExtendingClassDoubleLayercases. The first test fails with TS5115 when the change is off.Testing
z.json()in arrays, records, unions, tuples, maps,.pipe(), with and withoutskipLibChecknoGhostErrorskeyofGenericExtendingClassDoubleLayerOn all three of my projects this branch matches tsc 7.0.2 line for line, and every removed line is this TS5115.
Parity with Go
This departs from the pinned Go, in
resolveObjectTypeMembers,getPropertyOfTypeExandisStringIndexSignatureOnlyTypeWorker. OneTestLocalbaseline moves:mutuallyRecursiveInference.errors.txtloses the TS5114 that #64372 added and returns to its earlier baseline. That error was a false cycle, becauseX[X['a']]only reads membersXdeclares.I applied the same 54-line change to the Go checker at the pin.
TestLocal, the fourslash tests (includingTestNoGhostErrors) and the checker tests all pass there, apart from that one baseline. I'm happy to send the Go version to microsoft/TypeScript#64605 too, so the pin picks it up.Note
Fix checker to read declared members while base types resolve
ResolvingMembersstack tochecker.Checker.resolve_object_type_memberspushes the current type with its declared and working member tables before resolving base types, and pops it after inherited members are processed.get_property_of_typenow checks this stack first: if the actively resolving type declares a value property with that name, it returns it instead of re-entering member resolution. This fixes ghost errors from circular base-type references (e.g.class C<T> extends C<C["p"]>in checker_p21.rs).is_string_index_signature_only_type_workerin checker_p30.rs returns false early for an actively resolving type that declares a non-reserved value property.resolving_membersstack inChecker::get_property_of_type; other lookup paths are unchanged.Macroscope summarized 66f8110.
Summary by CodeRabbit