Skip to content

fix(generator): keep the literals of a nullable enum, type empty lists - #7

Merged
nadar merged 2 commits into
mainfrom
claude/nifty-rubin-gbcon2
Oct 8, 2026
Merged

nadar merged 2 commits into
mainfrom
claude/nifty-rubin-gbcon2

Conversation

@nadar

@nadar nadar commented Oct 8, 2026

Copy link
Copy Markdown
Member

Problem

A select field's value is a nullable enum, and the schema lists null among its members. That is correct OpenAPI 3.0.3: nullable: true alone does not allow null unless the enum lists it.

TypeMapper::literalUnion() drops an enum it thinks is unsafe, and its check is !is_scalar($value). Since is_scalar(null) is false, a single null member threw away the whole enum, so the generated type fell back to plain string:

size: object{
    value: string|null,                    // enum ["small", "medium", "large", null]
    options: \stdClass|array<int, mixed>,
    label: string|null
}|null,

Change

  • Nullable enums: a null member no longer disqualifies the enum. It becomes a trailing null, the same position the rest of the generator puts it in. enum: [null] maps to null, and arrays or objects inside an enum still fall back to string as before.
  • Empty lists: an array schema with maxItems: 0 can only ever be empty, so it maps to array{}. That makes options (a map of value => label, or [] when nothing is selected) \stdClass|array{}.
size: object{
    value: 'small'|'medium'|'large'|null,
    options: \stdClass|array{},
    label: string|null
}|null,

Tests

  • TypeMapperTest: nullable enum, null listed first, enum of only null, enum with an object member, maxItems: 0, a positive maxItems, the map-or-empty-list oneOf, and no double |null.
  • types/site fixture: category now has the real select-field shape (null in the enum, oneOf options). Its match assertion only stays exhaustive because of the fix.
  • blocks/full fixture: new nullable_enum and empty_list fields with PHPStan assertions.
  • TypesAccessPatternTest: at runtime, a selected option's options is a \stdClass, and an unselected one decodes to [].
  • Checked in reverse too: with the TypeMapper change reverted, 10 PHPUnit cases and 4 level-9 PHPStan assertions fail.

Worth a look

  • The deprecated flyo-generate-blocks output changes too. Profile promises byte-identical output for it. I applied the fix to both commands anyway: holding it back would keep the bug there, would need a flag passed through Kind::plan(), and would break the "a block class is the same under both commands" invariant that GoldenTest checks. UPGRADE.md gets a "From 3.6 to 3.7" note saying --check reports drift until the first regeneration. Adjust the version heading if the next release is numbered differently.
  • Separate commit: PHPStan repair for main. The regeneration from spec 2.49 (9d53bb5) made Block::getSlots() non-nullable. Since then the phpstan job on main fails on getSlots() ?? [] in two fixture assertions. The first commit removes the ?? [] so this PR (and main, once merged) is green again.

Verification

  • vendor/bin/phpunit: 298 tests OK
  • phpstan.neon, phpstan-generator.neon, phpstan-generated.neon: no errors
  • Both CLI --check smoke tests from the workflow pass

🤖 Generated with Claude Code

https://claude.ai/code/session_01UGTdQLpEoYyXhbJV1znWEW


Generated by Claude Code

claude added 2 commits October 8, 2026 11:42
The SDK regeneration from spec 2.49 made Block::getSlots() non-nullable,
so PHPStan's level 9 run over the generated fixtures reports the
`getSlots() ?? []` in two assertion files as a left side that is never
null, which has failed the phpstan job on main since then.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UGTdQLpEoYyXhbJV1znWEW
A select field's `value` lists `null` among its enum members, which is
how OpenAPI 3.0 lets a nullable enum hold null. TypeMapper treated any
non-scalar member as unsafe and dropped the whole enum, so the value was
typed `string|null` instead of its literal union. A `null` member now
becomes a trailing `null` in the union.

An array schema with `maxItems: 0` can only ever be empty, so it maps to
`array{}`. That makes a select field's `options`, a map of value =>
label or `[]` when nothing is selected, `\stdClass|array{}`.

This changes the output of both generator commands for every schema with
a select field, including the deprecated flyo-generate-blocks; UPGRADE.md
says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UGTdQLpEoYyXhbJV1znWEW
@nadar
nadar merged commit baeb387 into main Oct 8, 2026
8 checks passed
@nadar
nadar deleted the claude/nifty-rubin-gbcon2 branch October 8, 2026 11:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants