Skip to content

[0.321 Rust port] Internal compiler panic (EInvalidObjectKit at flow_js_utils.rs:1297) on recursive homomorphic mapped type (react-hook-form DeepRequired) #9448

Description

@matclayton

Again sorry for the Claude written issue, but this came out of the automate update, will try and validate this and find more details later on when I have time. Sorry figured it was better to report this now, than hold it back until I can fully understand it. Feel free to close if needed.

Flow version: 0.321.0 (regression from 0.318.0, which checked the same code with no errors)

Summary

The Rust-backed checker panics (internal compiler error, brings the server down with exit 78) instead of emitting a type error when checking react-hook-form's DeepRequired<T> type. The crash fires whenever a file instantiates an RHF form whose value type contains primitive fields.

The proximate cause is the recursive homomorphic mapped type bottoming out on a primitive: DeepRequired<T[K]> is evaluated where T[K] is string/boolean/etc., and the object-kit path panics rather than producing a graceful error. A simplified standalone version of the same shape yields a clean not-an-object error instead of crashing, so the panic appears specific to the full DeepRequired + FieldErrorsImpl<DeepRequired<T>> + template-literal-key combination in the real libdef — I was not able to reduce it to a minimal standalone snippet.

Triggering type (from the react-hook-form libdef)

declare export type DeepRequired<T> = T extends
    | BrowserNativeObject
    | Blob
    ? T
    : { [K in keyof T]-?: NonNullable<DeepRequired<T[K]>> };

// ... used downstream as:
declare export type FieldErrors<T: FieldValues = FieldValues> = Partial<
    FieldValues extends IsAny<FieldValues>
        ? any
        : FieldErrorsImpl<DeepRequired<T>>,
> & { root?: ({ [key: string]: GlobalError } & GlobalError) };

Checking any module that instantiates FieldErrors<SomeForm> (i.e. uses useForm<SomeForm>()) where SomeForm has primitive fields triggers the panic.

Expected behavior

Either accept the type or emit a normal type error — never panic. (0.318.0 / OCaml checks the entire codebase using this libdef with No errors!.)

Actual behavior (0.321.0)

The server crashes on startup / full check:

thread '<unnamed>' panicked at crates/flow_typing_flow_common/src/flow_js_utils.rs:1297:21:
Non speculating: Speculative(SpeculativeError(EInvalidObjectKit(EInvalidObjectKitData {
  reason: VirtualReasonInner { desc: RString,
    loc: ... LibFile("react-hook-form_v7.x.x.js") line: 255 ...,   // the `{ [K in keyof T]-?: ... }` mapped type in DeepRequired
    def_loc_opt: Some(... SourceFile("<a form component>") ...) },  // the concrete form field
  reason_op: VirtualReasonInner { desc: RObjectType, loc: ... line 255 ... },
  use_op: Op(TypeApplication {
    type_: ... RTypeApp(RType(Name("DeepRequired"))) ... line 276 ...  // FieldErrorsImpl<DeepRequired<T>>
  })
})))

Unhandled exception: Non speculating: Speculative(SpeculativeError(EInvalidObjectKit(...)))
Could not start Flow server!   (exit code 78)

desc varies with the offending field type (RString, RBoolean, …), confirming the trigger is the recursion reaching a primitive.

Workaround

Bottoming out the recursion on non-object types stops the panic:

declare export type DeepRequired<T> = T extends
    | BrowserNativeObject
    | Blob
    ? T
    : T extends { ... }
      ? { [K in keyof T]-?: NonNullable<DeepRequired<T[K]>> }
      : T;

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions