Skip to content

fix(preprocessor): validate compound type declarations and class-scope type keywords - #65

Open
AlessioGiacobbe wants to merge 2 commits into
swoole:masterfrom
AlessioGiacobbe:split/compound-type-declarations
Open

fix(preprocessor): validate compound type declarations and class-scope type keywords#65
AlessioGiacobbe wants to merge 2 commits into
swoole:masterfrom
AlessioGiacobbe:split/compound-type-declarations

Conversation

@AlessioGiacobbe

Copy link
Copy Markdown
Contributor

Compound type declarations were not validated for well-formedness — all of these compiled, each a Zend compile fatal (probed individually): duplicate union members after alias/namespace resolution (int|string|int, Foo|\Foo, iterable expanded so iterable|array names the overlapping component); bool/true/false overlaps (bool|false, true|false → "bool must be used instead"); mixed/void/never inside unions; ?mixed, ?null, ?void, ?never; non-class standard types and duplicates inside intersections; self/static return types outside class scope (closures exempt, as Zend compiles them); duplicate implements entries for classes and enums.

One validation helper runs from a resolveTypeDecl override, so parameters, returns, properties, constants, and closures share the same pass without double-firing. use T, T is deliberately still accepted — Zend silently dedupes trait use (probed).

Part of the split of #39.

…e type keywords

resolveTypeDecl now runs a shared well-formedness pass before resolving,
so parameters, returns, properties, class/interface constants, and
closure signatures all obey Zend's compile-time compound-type rules
(each probed on 8.4.13):

- duplicate union members, case-insensitive and after alias/namespace
  resolution ("Duplicate type int is redundant", "Duplicate type
  App\Sub\Thing is redundant"); iterable is expanded to
  array|Traversable first, so iterable|array and iterable|\Traversable
  report the overlapping component exactly like Zend, while a
  namespace-local Traversable stays legal
- bool with false/true names the literal as the duplicate in either
  order; true|false demands bool ("Type contains both true and false,
  bool must be used instead")
- mixed/void/never inside a union ("... can only be used as a
  standalone type"), ?mixed ("Type mixed cannot be marked as nullable
  since mixed already includes null"), ?null, ?void, ?never
- intersection members must be class types ("Type int cannot be part
  of an intersection type"); duplicate intersection members are
  redundant; self/parent/static keep the established TypeCheckGenerator
  diagnostic; redundancy between whole DNF groups is not checked (Zend
  uses a distinct "Type X&Y is redundant with type X&Y" pass)
- self/static return types on free functions ("Cannot use \"static\"
  when no class scope is active"); closures keep accepting them since
  they may be bound to a scope later, matching Zend
- duplicate interfaces in an implements list, for classes and enums
  ("Class A cannot implement previously implemented interface I");
  duplicate trait use stays legal - Zend deduplicates it silently

@matyhtf matyhtf left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The focused tests pass (14/14), but three Zend type rules still compile successfully:

  1. object absorbs every class type:
class Foo {}
function f(object|Foo $value): void {}

Zend: Type Foo|object contains both object and a class type, which is redundant. Track object versus resolved class members in both member orders.

  1. Whole DNF groups cannot be skipped:
interface A {}
interface B {}
function f((A&B)|(B&A) $value): void {}

Zend rejects the second intersection as redundant. It also rejects (A&B)|A because the intersection is more restrictive than A. Canonicalize intersection member sets (order-insensitive) and check exact/restrictive DNF redundancy.

  1. Class-scope keywords are only checked for a bare return node. They also occur in parameters and nested union/DNF returns:
function f(self $value): void {}
function g(): self|stdClass {}

Both must fail with Cannot use "self" when no class scope is active; this PR compiles them. Validate self/static recursively for every declaration context. static must additionally remain return-only.

Please add negative tests for these forms. The current comment explicitly saying whole-DNF redundancy is not checked documents a known PHP incompatibility, so this is not ready to merge as the compound-type validator.

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