Problem
builder.dart's fast bailout (lines ~136-143 in 3.0.0-dev.3):
final hasSolidAnnotation = source.contains(_solidAnnotationHint);
final hasProviderHint = source.contains(_providerCallHint) || source.contains(_environmentCallHint);
if (!hasSolidAnnotation && !hasProviderHint) {
await buildStep.writeAsString(outputId, source); // verbatim passthrough
return;
}
means a pure consumer — a file that holds a @SolidState-bearing class via plain constructor injection but declares no @Solid* annotation and no provider call site of its own — never enters the transform pipeline. The #104/#105 registry seeding fix only helps files that are being processed, so it never runs for these files: their cross-file session reads stay silently un-lowered.
Every golden fixture in the #105 suite accidentally masks this: each consumer class carries its own @SolidState control field (loadCount), which is exactly what defeats the bailout.
Severity — reproduced, worse than a wrong comparison
In a real app (advisor frontend), making the auth repository's session a public @SolidState field and regenerating produced, for an annotation-free router.dart guard:
dart fix's dead_code/unnecessary_null_comparison passes saw the un-lowered Signal<Session?> as always-non-null and collapsed AuthGuard.onNavigation to an unconditional resolver.next(); return; — a generated authentication bypass, surfaced only as dead_code warnings;
- the inverse login-route guard collapsed to unconditionally redirect every
/login visit;
session!.token in annotation-free repositories became session.token → compile error (getter 'token' isn't defined for 'Signal<Session?>') — the lucky failure mode.
Candidate fix
Before the passthrough, do the cheap syntactic pass that #105 already implements: parse the unit, seed candidate type names from instance fields / constructor params (existing skip-set + local-shadowing + combinator rules), and walk the relative imports' registries. Enter the full pipeline iff the resulting registry is non-empty; keep the verbatim passthrough otherwise, preserving the fast path for genuinely solid-free files. Plus a golden fixture whose consumer has zero own annotations — the shape the current suite lacks.
Problem
builder.dart's fast bailout (lines ~136-143 in 3.0.0-dev.3):means a pure consumer — a file that holds a
@SolidState-bearing class via plain constructor injection but declares no@Solid*annotation and no provider call site of its own — never enters the transform pipeline. The #104/#105 registry seeding fix only helps files that are being processed, so it never runs for these files: their cross-filesessionreads stay silently un-lowered.Every golden fixture in the #105 suite accidentally masks this: each consumer class carries its own
@SolidStatecontrol field (loadCount), which is exactly what defeats the bailout.Severity — reproduced, worse than a wrong comparison
In a real app (advisor frontend), making the auth repository's session a public
@SolidStatefield and regenerating produced, for an annotation-freerouter.dartguard:dart fix'sdead_code/unnecessary_null_comparisonpasses saw the un-loweredSignal<Session?>as always-non-null and collapsedAuthGuard.onNavigationto an unconditionalresolver.next(); return;— a generated authentication bypass, surfaced only asdead_codewarnings;/loginvisit;session!.tokenin annotation-free repositories becamesession.token→ compile error (getter 'token' isn't defined for 'Signal<Session?>') — the lucky failure mode.Candidate fix
Before the passthrough, do the cheap syntactic pass that #105 already implements: parse the unit, seed candidate type names from instance fields / constructor params (existing skip-set + local-shadowing + combinator rules), and walk the relative imports' registries. Enter the full pipeline iff the resulting registry is non-empty; keep the verbatim passthrough otherwise, preserving the fast path for genuinely solid-free files. Plus a golden fixture whose consumer has zero own annotations — the shape the current suite lacks.