Skip to content

Extract CodeGen namespace from LintMixinCommand#1716

Merged
alganet merged 1 commit intoRespect:3.1from
alganet:codegen
Mar 11, 2026
Merged

Extract CodeGen namespace from LintMixinCommand#1716
alganet merged 1 commit intoRespect:3.1from
alganet:codegen

Conversation

@alganet
Copy link
Member

@alganet alganet commented Mar 5, 2026

This change introduces no BC break.

Attributes from src-dev are not used in runtime, so it's fine for them to be referenced by files in src for now.

Goal here is to start shaping a Respect/CodeGen project that can move all of this complexity outside of Validation and also support projects like Respect/StringFormatter later.


Replace hardcoded validator class lists with a declarative #[Mixin] attribute and extract the mixin generation logic into a reusable CodeGen namespace under src-dev/CodeGen/.

The new MixinGenerator discovers prefix definitions and filtering rules by scanning #[Mixin] attributes on the target namespace's classes, removing the need for hardcoded configuration. It supports configurable interface types (Builder for __callStatic, Chain for __call) with custom suffixes, return types, and root extends.

This is the first step toward extracting the code generation into a standalone package that can map __call/__callStatic to any namespace, possibly for Respect/StringFormatter and any kind of project in the future.

@codecov
Copy link

codecov bot commented Mar 5, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.45%. Comparing base (84d7436) to head (dd4b9ac).
⚠️ Report is 4 commits behind head on 3.1.

Additional details and impacted files
@@            Coverage Diff            @@
##                3.1    #1716   +/-   ##
=========================================
  Coverage     99.45%   99.45%           
  Complexity     1015     1015           
=========================================
  Files           194      194           
  Lines          2384     2384           
=========================================
  Hits           2371     2371           
  Misses           13       13           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@alganet alganet requested a review from Copilot March 5, 2026 17:54
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@alganet alganet requested a review from Copilot March 5, 2026 20:47
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 44 out of 44 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Replace hardcoded validator class lists with a declarative #[Mixin]
attribute and extract the mixin generation logic into a reusable
CodeGen namespace under src-dev/CodeGen/.

The new MixinGenerator discovers prefix definitions and filtering
rules by scanning #[Mixin] attributes on the target namespace's
classes, removing the need for hardcoded configuration. It supports
configurable interface types (Builder for __callStatic, Chain for
__call) with custom suffixes, return types, and root extends.

This is the first step toward extracting the code generation into a
standalone package that can map __call/__callStatic to any namespace,
possibly for Respect/StringFormatter and any kind of project in the
future.
@alganet alganet marked this pull request as ready for review March 5, 2026 21:10
@alganet alganet requested a review from henriquemoody March 5, 2026 21:10
Copy link
Member

@henriquemoody henriquemoody left a comment

Choose a reason for hiding this comment

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

I think the CodeGen part could be extract, but I'm not super sure about it. The Mixin attribute seems more like a Validation concern than a CodeGen concern. If we want to make it as a CodeGen concern, perhaps it should be something more generic, like tags or labels that we could use when generating those mixings. For example, another CodeGen tag/label could be: prefix and having other tags for describing the order of the arguments.

@alganet
Copy link
Member Author

alganet commented Mar 10, 2026

@henriquemoody I'm not sure I understand your comment.

The StringFormatter package could definitely benefit from this as it is (generic enough), and in theory any other PHP project that maps magic methods to namespaces.

While this was sitting in review, I got to think about the naming though, and it's possible that I will rename Mixin to something else, to avoid confusion with the idea of multiple inheritance (ruby mixins for example)

@henriquemoody
Copy link
Member

henriquemoody commented Mar 10, 2026

@alganet, sorry if my previous comment was a bit vague.

I like this change, and not I'm saying we shouldn't have a generic CodeGen, quite the opposite, I think we should do it, and be more generic. My main concern is actually more structural regarding separation of concerns.

Right now, the attribute design feels heavily tied to how Respect/Validation behaves (and granted, like other packages we have, like StringFormatter and Assertion). If we extract this into a standalone Respect/CodeGen package (so it can be used by other packages), the CodeGen package shouldn't inherently "know" about our the fact that we're using it to create a mixin.

When I mentioned generic concepts like tags or labels, I meant that the CodeGen tool should rely on domain-agnostic metadata. Instead of an attribute that implies our specific use case, we could use composable instructions.

#[CodeGen\Tag('exclude', value: ['all', 'key', 'property'])]
#[CodeGen\Tag('prefix', value: 'key')]
#[CodeGen\Tag('prefixParameter', value: true)]
final readonly class Key implements KeyRelated

This way, Respect/CodeGen remains completely unopinionated. The client code, that uses CodeGen, would make decisions based on those "tags".

Does that make more sense regarding why I think the current approach feels too coupled to the Validation domain?

@alganet
Copy link
Member Author

alganet commented Mar 10, 2026

@henriquemoody

I still don't get it! You described the shape of how the Attributes should be: separate instead of a single Attrribute with many named parameters.

Current:

#[CodeGen\Tag(exclude: ['all', 'key', 'property'], prefix: 'key', prefixParameter: true);

Your proposal:

#[CodeGen\Tag('exclude', value: ['all', 'key', 'property'])]
#[CodeGen\Tag('prefix', value: 'key')]
#[CodeGen\Tag('prefixParameter', value: true)]

I don't understand how that makes it more generic. It's just a different signature. All of these (exclude, prefix, prefixParameter) are needed for MixinGenerator.

I'm a big fan of decoupling, but in this case I'm not seeing the actual proposal here. How would we change or refactor what MixinGenerator does in light of this new design? If it stays the same, then it doesn't decouple anything more.

@henriquemoody
Copy link
Member

henriquemoody commented Mar 10, 2026

Before I answer anything, let me take a step back and ask you this question:

Do you want to extract the CodeGen part into a library that serves only our Respect packages or would it be aimed at generic code generation, like generating method signatures from a constructor, for example? Is it generic or just for us to reuse code around?

@alganet
Copy link
Member Author

alganet commented Mar 10, 2026

@henriquemoody my goal is what I've described in the PR description. I want to start shaping a CodeGen library that could be generic later down the line, but first I want to exclusively solve maintenance and reuse for Validation and StringFormatter.

It's an iterative approach to design. I don't want to think of everything beforehand, I want the real use cases we have to guide us. If it ends up being reusable beyond Respect, cool. If not, then it's fine too.

If you believe there's an use case for which there's a clear gap in the PHP ecosystem and this somehow intersects with that gap, I'd love to know what that use case is! It would certainly affect how I approach it.

@henriquemoody
Copy link
Member

I understand that we don't want to think about all scenarios, but since this change will be shipped in the next version, that attribute will be something we'd need to think twice before changing.

The clear gap in the PHP ecosystem is the same reason why we built this. Have we had a PHP library that would extract the signature of a method and allow you to regenerate it with modifications we wouldn't have created this. A similar problem would be true for other builders that use __call() and __callStatic() that would create instances of classes with a similar signature to the magic method, or even one that doesn't use a magic method but exposes a signature similar to another signature.

I can stand behind just focusing on the use case we have, but if we want to move that into a different package, and if youdon't want to have a Respect\Dev (which I would be up for), I think we should change the namespace to Respect\CodeGen at least.

@alganet
Copy link
Member Author

alganet commented Mar 10, 2026

@henriquemoody

that attribute will be something we'd need to think twice before changing.

Why? Attributes are inert in PHP unless loaded. It doesn't change the public API of Respect\Validation. It also doesn't change how people write custom validators. We can change it as much as we want, this is src-dev and not shipped or used in runtime by end-users of the package.

I can stand behind just focusing on the use case we have,

But what are the other use cases though? Knowing them would be precious to me, because I could think of design further for next iterations. Perhaps you have something in mind, but I don't know what it is.

My current approach solves the case where a namespace (such as Validators/) has classes that map to nodes in a chain (via __call/__callStatic). The parametrization happens in the constructor of MixinGenerator. You can see how it is decoupled from Respect by how LintMixinCommand is structured now (the diff is hidden by default, you might have missed it).

change the namespace to Respect\CodeGen at least.

The naming is not what I'm talking about, right? Nor details about separate attributes vs parameters. I understand those are relevant cosmetic details, but don't change much the abstractions involved, they're details.

You said it yourself, right?

My main concern is actually more structural regarding separation of concerns.

Mine are too! However, the actual implementation is not in the attributes, is within MixinGenerator, which just reads them. I could separate the prefix and so on in separate attributes, but that won't change any of the generator structure.

If you can comment there on your idea for separating how prefix other inputs work inside MixinGenerator, perhaps I would understand it better and follow up on your suggestion!

@henriquemoody
Copy link
Member

Why? Attributes are inert in PHP unless loaded. It doesn't change the public API of Respect\Validation.

Fair point, I'm fine with keeping it like that and refactoring later.

But what are the other use cases though?

I can only think about our use-cases.

The naming is not what I'm talking about, right?

No it's not. I was just talking about the fact that when we extract it to a different package we'd have to rename it, unless Respect\CodeGen namespace will start with Respect\Dev. As I said, I'm fine with keeping it like that and refactoring later.

If you can comment there on your idea for separating how prefix other inputs work inside MixinGenerator

For context on where my head was: I'm planning on adding @psalm-assert tags to Assertion's mixins. When that happens, I'll likely need more metadata that doesn't belong in MixinGenerator as it is. My hunch is I'll end up not using MixinGenerator for that and instead breaking things into smaller composable pieces, but that's a problem for later.


I will approve this PR, there's nothing blocking from my perspective. Let's merge and see how it will evolve.

@alganet
Copy link
Member Author

alganet commented Mar 11, 2026

@henriquemoody thanks! Any further improvement to support automated @psalm-assert annotations would be welcome.

My next steps are:

  • Investigate mixins with more than one param constructor (it seems the old generator only used one param, I kept that to avoid having huge changes. Ideally, we should be able to allow multiple params)
  • Investigate generating the Prefix Transformer too. I believe we should be able to automate this, further reducing maintenance on that part. This is one of the extra use cases I had in mind, and I think it will further exercise how modular CodeGen could be.

@alganet alganet merged commit 91fb70f into Respect:3.1 Mar 11, 2026
7 checks passed
@alganet alganet deleted the codegen branch March 11, 2026 14:06
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.

3 participants