Skip to content

Feature Request: Optional instance method generation for pattern matching (backward compatibility) #1346

@D-Smarak

Description

@D-Smarak

Problem Statement

When upgrading from freezed 2.x to 3.x, all existing code that uses pattern-matching methods (.when(), .maybeWhen(), .map(), .maybeMap()) as instance methods breaks with compilation errors:

error: The method 'maybeWhen' isn't defined for the type 'MyState'

Impact on Large Codebases

For projects with hundreds of freezed classes:

  • Our codebase: 716 freezed classes, 442+ pattern matching call sites
  • Result: 442+ compilation errors after upgrading to freezed 3.x
  • Refactoring effort: 20-40 hours of manual work
  • Risk: High chance of introducing bugs during mass refactoring

This effectively blocks adoption of freezed 3.x for large existing codebases.

Root Cause

Freezed 2.x Behavior

Pattern methods were generated as instance methods on the mixin:

mixin _$MyState {
  TResult when<TResult>(...) { ... }  // Instance method
  TResult maybeWhen<TResult>(...) { ... }
}

Freezed 3.x Behavior

Pattern methods are only generated as extension methods:

extension $MyStateExtension on MyState {
  TResult when<TResult>(...) { ... }  // Extension only
}

Existing code calling .maybeWhen() as an instance method fails to compile.

Proposed Solution

Add a build.yaml configuration option to generate pattern methods as instance methods (in addition to extensions):

# build.yaml
targets:
  $default:
    builders:
      freezed:
        options:
          # New option (default: false for backward compatibility)
          generate_pattern_methods_as_instance: true

Implementation Approach

When this option is enabled:

  1. Generate pattern methods in the mixin (as instance methods)
  2. Also generate extension methods (for new code)
  3. Both syntaxes work simultaneously

Result:

// ✅ Instance method (freezed 2.x style) - works
myState.maybeWhen(
  success: (data) => print(data),
  orElse: () => print('other'),
);

// ✅ Extension method (freezed 3.x style) - also works
myState.maybeWhen(
  success: (data) => print(data),
  orElse: () => print('other'),
);

Benefits

  1. Zero breaking changes for existing codebases
  2. Gradual migration path: Teams can migrate at their own pace
  3. Backward compatible: Old code continues to work
  4. Forward compatible: New code can use either syntax
  5. Opt-in: Doesn't affect users who don't need it

Proof of Concept

We've implemented this as a local patch and it works perfectly:

  • ✅ 0 compilation errors (down from 442+)
  • ✅ All 716 freezed classes work correctly
  • ✅ Full freezed 3.x feature support
  • ✅ Zero code changes required
  • ✅ Production-tested for [X weeks]

Technical Implementation

Modified three template files:

  1. pattern_template.dart: Added patternMethods() function (generates methods without extension wrapper)
  2. abstract_template.dart: Inject patternMethods() into mixin body
  3. concrete_template.dart: Ensure concrete classes use with _$ClassName

Code changes: ~50 lines total
Complexity: Low
Performance impact: None (same methods, different location)

Use Cases

This feature would benefit:

  1. Large existing codebases (hundreds of freezed classes)
  2. Teams with limited refactoring time
  3. Projects in active development (can't pause for mass refactoring)
  4. Gradual migration scenarios (migrate module by module)

Alternative Considered

Mass refactoring: Refactor all 442+ call sites to use Dart 3 switch expressions

  • ❌ Time-consuming (20-40 hours)
  • ❌ High risk of bugs
  • ❌ Blocks other development work
  • ❌ Not feasible for all teams

Willingness to Contribute

If this feature is acceptable, I'm willing to:

  • Submit a PR with the implementation
  • Add tests for the new option
  • Update documentation
  • Provide migration guide

Additional Context

Related

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions