Skip to content

fix(preprocessor): enforce property-hook placement rules for class properties - #63

Merged
matyhtf merged 3 commits into
swoole:masterfrom
AlessioGiacobbe:split/property-hook-rules
Sep 2, 2026
Merged

fix(preprocessor): enforce property-hook placement rules for class properties#63
matyhtf merged 3 commits into
swoole:masterfrom
AlessioGiacobbe:split/property-hook-rules

Conversation

@AlessioGiacobbe

Copy link
Copy Markdown
Contributor

Property-hook placement rules were unenforced on the class path (the interface path already had them): hooks on static properties ("Cannot declare hooks for static property"), hooks on readonly properties incl. readonly classes ("Hooked properties cannot be readonly"), abstract hooked properties in non-abstract classes, abstract properties without hooks, abstract hooked properties with a default or with all hooks bodied, and bodiless hooks on non-abstract properties (previously the lowering silently fabricated a concrete accessor).

Each rule and its precedence (static → readonly → abstract) probed against Zend 8.4.13; traits keep abstract hooked properties.

Part of the split of #39.

@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 abstract-property branch is missing two Zend modifier conflicts; both currently dry-compile:

abstract class A {
    abstract private int $x { get; }
}

Zend: Property hook cannot be both abstract and private. Unlike abstract private trait methods, an abstract private property hook is also forbidden inside a trait. Protected abstract hooks remain legal.

abstract class A {
    abstract public int $x { final get; }
}

Zend: Property hook cannot be both abstract and final. A bodiless hook must not carry final.

Please add these checks with the same Zend precedence and negative tests for both. The existing focused tests pass (8/8), but do not cover abstract/private or abstract/final combinations.

@AlessioGiacobbe
AlessioGiacobbe force-pushed the split/property-hook-rules branch from 277fde3 to 70985b6 Compare September 2, 2026 08:21
@AlessioGiacobbe

Copy link
Copy Markdown
Contributor Author

Added, with the precedence pinned by probes first: static → readonly → per-hook final+private ("Property hook cannot be both final and private" — it wins your combined abstract private int $x { final get; } case and also fires in traits and on body-carrying hooks) → then, for bodiless hooks, abstract+private and abstract+final, both of which fire in traits too (unlike abstract private trait methods) and precede the default-value and at-least-one-abstract-hook rules.

One probe finding worth noting: a final hook with a body on an abstract property is legal in Zend (abstract public int $x { final get => 1; set; } compiles), so the final check is strictly per-bodiless-hook.

Five new fixtures (abstract-private in class and trait, abstract-final, the final-private precedence case, and the protected-abstract positive), all Zend-validated; 27/27 across the hook suites.

…operties

The interface path already validated hook placement; class and trait
properties accepted every combination. parseClassPropertyDef now
mirrors Zend's compile-time rules (probed on 8.4.13, including the
precedence order static -> readonly -> abstract rules):

- hooks on a static property ("Cannot declare hooks for static
  property")
- hooks on a readonly property, including properties made readonly by
  a `readonly class` ("Hooked properties cannot be readonly")
- `abstract` on a hook-less property ("Only hooked properties may be
  declared abstract")
- abstract hooked property with a default value ("Cannot specify
  default value for virtual hooked property A::$x")
- abstract hooked property whose hooks all have bodies ("Abstract
  property A::$x must specify at least one abstract hook")
- abstract hooked property in a non-abstract class; traits stay exempt
  (the consuming class satisfies the hook) and enums are already
  rejected by the property ban
- bodiless hook on a non-abstract property, in classes and traits
  ("Non-abstract property hook must have a body"); previously the
  lowering fabricated a concrete backing-store accessor for it
…private hooks

Three hook-level modifier conflicts Zend rejects at compile time were
still accepted by the class/trait property path (all probed on 8.4.13):

- `abstract private int $x { get; }` — an abstract (bodiless) hook must
  be implemented by a subclass, which private visibility forbids
  ("Property hook cannot be both abstract and private"). Unlike abstract
  private trait methods, Zend does NOT exempt traits from this rule.
- `abstract public int $x { final get; }` — a bodiless hook must stay
  overridable to ever gain a body ("Property hook cannot be both
  abstract and final").
- `private int $x { final get => 1; }` — a final hook on a private
  property is meaningless because private members cannot be overridden
  ("Property hook cannot be both final and private").

Diagnostic precedence follows Zend: static, then readonly, then the
per-hook final+private conflict (which wins over both abstract
conflicts: `abstract private int $x { final get; }` reports
final+private), then per bodiless hook abstract+private before
abstract+final, all ahead of the default-value and
at-least-one-abstract-hook rules. Protected abstract hooks remain
legal in classes and traits.
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