Skip to content

Root file order causes tsgo to miss recursive conditional type assignability error #63859

Description

@tomquist

Steps to reproduce

Create these two dependency-free files.

warmup.ts

type SomeJSONSchema = UncheckedJSONSchemaType<Known, true>

type JSONType<T extends string, Partial extends boolean> =
  Partial extends true ? T | undefined : T

type UncheckedJSONSchemaType<T, Partial extends boolean> = (
  | { anyOf: readonly UncheckedJSONSchemaType<T, Partial>[] }
  | (T extends string
      ? { type: JSONType<"string", Partial> }
      : T extends readonly any[]
        ? {
            type: JSONType<"array", Partial>
            items: UncheckedJSONSchemaType<T[0], false>
          }
        : T extends Record<string, any>
          ? {
              type: JSONType<"object", Partial>
              properties?: Partial extends true
                ? Partial<UncheckedPropertiesSchema<T>>
                : UncheckedPropertiesSchema<T>
              required: readonly (keyof T)[]
            }
          : never)
) & { [keyword: string]: any }

type JSONSchemaType<T> = UncheckedJSONSchemaType<T, false>

type Known = { [key: string]: Known } | Known[] | string

type UncheckedPropertiesSchema<T> = {
  [K in keyof T]-?: UncheckedJSONSchemaType<T[K], false> & Nullable<T[K]>
}

type Nullable<T> = {
  enum?: readonly T[]
}

interface CompletionParameters {
  responseFormat?:
    | { type: "json_object" }
    | {
        type: "json_schema"
        json_schema: { schema?: SomeJSONSchema }
      }
}

interface Item {
  value: string
}

const itemSchema: JSONSchemaType<Item> = {
  type: "object",
  properties: { value: { type: "string" } },
  required: ["value"],
}

interface WarmResult {
  items: Item[]
}

const warmResultSchema: JSONSchemaType<WarmResult> = {
  type: "object",
  properties: {
    items: { type: "array", items: itemSchema },
  },
  required: ["items"],
}

target.ts

interface Result {
  items: Item[]
}

const resultSchema: JSONSchemaType<Result> = {
  type: "object",
  properties: {
    items: { type: "array", items: itemSchema },
  },
  required: ["items"],
}

class TargetPrompt {
  completionParameters: CompletionParameters = {
    responseFormat: {
      type: "json_schema",
      json_schema: { schema: resultSchema },
    },
  }
}

Versions tested:

  • TypeScript 6.0.3
  • tsgo 7.0.2, git commit 2bd066d87f5bafd315be9f40889d0a60b9e58e0b

Run the files in this order:

  • tsc --ignoreConfig --strict --noEmit --pretty false warmup.ts target.ts
  • tsgo --ignoreConfig --strict --noEmit --pretty false warmup.ts target.ts

Reversing the root-file order makes both compilers accept the program:

  • tsc --ignoreConfig --strict --noEmit --pretty false target.ts warmup.ts
  • tsgo --ignoreConfig --strict --noEmit --pretty false target.ts warmup.ts

Behavior with typescript@6.0

The warmup-first command exits with code 2:

target.ts(17,22): error TS2322: Type 'JSONSchemaType<Result>' is not assignable to type 'SomeJSONSchema | undefined'.
  Type '{ anyOf: readonly UncheckedJSONSchemaType<Result, false>[] } & { [keyword: string]: any }' is not assignable to type 'SomeJSONSchema | undefined'.

Behavior with tsgo

The same warmup-first command exits successfully with no diagnostics.

Expected behavior

tsgo should report the same assignability error as TypeScript 6 for the same root-file order.

warmResultSchema is unrelated to and unused by TargetPrompt. Its presence and root-file position should not change whether resultSchema is assignable to SomeJSONSchema.

The types are minimized from AJV’s recursive JSONSchemaType<T> and SomeJSONSchema types. AJV documents that these widened types are incompatible in ajv-validator/ajv#1988, so tsgo’s successful result appears to be a false negative. This may relate to recursive relation caching or bailout order.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Not a DefectThis behavior is one of several equally-correct options

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions