Skip to content

fix(mix_generator): compare generic target tear-offs structurally - #1105

Open
kauandotnet wants to merge 1 commit into
mainfrom
stargate/generic-tearoff-structural
Open

kauandotnet wants to merge 1 commit into
mainfrom
stargate/generic-tearoff-structural

Conversation

@kauandotnet

Copy link
Copy Markdown

validateGenericTargetTearOff compared the tear-off's type parameters with the class's by element identity. Under build_runner, analyzer 11 and later can return the constant's function type with freshly created type parameters that have no enclosing element. As a result, a direct RemixCheckboxGroupItem.new target was rejected as instantiated once other libraries had been resolved in the same build. A filtered build of the one file passed.

The check now compares shape instead:

  • one type parameter per class type parameter, with the same name and bound;
  • each declared by the class or by no element, never by a typedef;
  • a return type that instantiates the class with exactly those parameters.

Instantiated and typedef tear-offs are still rejected.

Found in: Stargate Studio's build on analyzer 13, where 2.2.1 rejects its checkbox group. Stargate uses this commit through a temporary path override until a release carries the fix.

Testing: this PR adds no regression test. The failure only appears in a multi-library build_runner run.

validateGenericTargetTearOff compared the tear-off type's type parameters
with the class's by element identity. Under build_runner, analyzer 11 and
later can return the constant's function type with freshly created type
parameters that have no enclosing element, so a direct
RemixCheckboxGroupItem.new target was rejected as instantiated once other
libraries had been resolved in the same build (a filtered build of the one
file passed).

Check the shape instead: one type parameter per class type parameter, with
the class parameter's name and bound, declared by the class or by no element
(never by a typedef), and a return type that instantiates the class with
exactly those parameters. Instantiated and typedef tear-offs are still
rejected.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-24T15:06:31.938549Z e68d862 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@leoafarias leoafarias left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I looked at the fix and the existing mix_generator validation suite — the structural compare is the right direction for the analyzer 11+ enclosingElement == null case, but I’m holding merge until we have tests for it.

Please add coverage in packages/mix_generator/test/integration/generator_validation_test.dart (same style as the other @MixableSpec(target:) cases): a direct generic tear-off that must pass, plus rejects for typedef / instantiated / nullable type-arg shapes. Then run the mix_generator tests and paste the result here.

Stargate already depends on this via a path override, so I’d rather not ship without a regression net.

declaredBy.library.uri != owner.library.uri)) ||
parameter.bound?.getDisplayString() !=
classParameter.bound?.getDisplayString()) {
return false;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Need a unit/integration test that hits this path when the tear-off type params have enclosingElement == null (the analyzer 11+ / build_runner case that broke Stargate). The single-file filtered build isn’t enough — that’s why the bug slipped through.

Also please reject nullable type args here (C<T?>). Right now we only check TypeParameterType + name, so a nullable argument can still look “structural” and get accepted. Same contract as “direct, uninstantiated”.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ignore the nullable type-arg note above — that one’s on me. Only ask here is the regression test for the enclosingElement == null path, then run the mix_generator suite and paste the result.

Copy link
Copy Markdown
Collaborator

Quick clarification on my review: the only ask is adding the regression tests (especially the analyzer 11+ enclosingElement == null path) and running the mix_generator suite. Ignore the nullable type-arg note in the inline comment.

@leoafarias leoafarias left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

A few more concrete asks before I’d merge — same hold as before, just spelling out the test shape and two code gaps.

returnType is! InterfaceType ||
returnType.typeArguments.length != own.length) {
return false;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Blocker: please unit-test this path with a FunctionType whose type params have enclosingElement == null (orphan params), not a full multi-library build_runner run. That’s the analyzer 11+ case that bit Stargate — if we only cover it via an integration build, it can regress quietly.

parameter.bound?.getDisplayString() !=
classParameter.bound?.getDisplayString()) {
return false;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Blocker: reject nullable type args here (C<T?>). Right now we only check TypeParameterType + name, so a nullable argument can still look structural and get accepted. Same “direct, uninstantiated” contract as the rest of this guard.

if (parameter.name != classParameter.name ||
(declaredBy != null &&
(declaredBy is! InterfaceElement ||
declaredBy.name != owner.name ||

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Medium: comparing bounds with getDisplayString() is a bit fragile — equivalent bounds can disagree as text. Prefer the analyzer type system (or an element-level bound compare) so we don’t false-reject. Fine as a follow-up if you want the Stargate unblock first, but I’d rather do it in this PR.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants