Skip to content

Conversation

@nank1ro
Copy link
Member

@nank1ro nank1ro commented Dec 9, 2025

  • FEAT: Allow providers declared in the same ProviderScope to depend on each other, by leveraging the order of declaration. This simplifies the development experience and prevents circular dependencies by design.
  • FEAT: Add debugName parameter to providers for easier debugging, allowing better identification of providers in error messages and logs.

This now works:

import 'package:disco/disco.dart';
import 'package:flutter/material.dart';
import 'package:flutter_solidart/flutter_solidart.dart';

final counterProvider =
    Provider((_) => Signal(0), debugName: 'counterProvider');

final doubleCounterProvider = Provider((context) {
  final counter = counterProvider.of(context);
  return Computed(() => counter.value * 2);
}, debugName: 'doubleCounterProvider');

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Counter Example',
      home: Counter(),
    );
  }
}

class Counter extends StatelessWidget {
  const Counter({super.key});

  @override
  Widget build(BuildContext context) {
    return ProviderScope(
      providers: [
        counterProvider,
        doubleCounterProvider,
      ],
      child: Builder(builder: (context) {
        return Scaffold(
          body: SignalBuilder(
            builder: (_, __) {
              final c = counterProvider.of(context);
              final dc = doubleCounterProvider.of(context);
              return Text('counter: ${c.value}, double: ${dc.value}');
            },
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: () => counterProvider.of(context).value++,
            child: const Icon(Icons.add),
          ),
        );
      }),
    );
  }
}

while by reversing the order of the providers declaration (doubleCounterProvider before counterProvider) you get this runtime error:

Screenshot 2025-12-11 at 13 03 55

This eliminates the possibility to create a circular dependency.

Summary by CodeRabbit

  • New Features

    • Optional debugName for providers to aid debugging
    • Providers in the same scope may depend on each other by declaration order
  • Bug Fixes

    • Forward-reference detection and clearer provider-related errors to prevent init/circular issues
  • Documentation

    • Examples updated to show single-scope dependency/order patterns; minor docs fixes
  • Tests

    • Expanded benchmark suite; CI workflow added to run and publish benchmark results
  • Chores

    • Package bumped to 2.0.0; benchmarks excluded from publishing; analyzer ignores added for select diagnostics

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 9, 2025

Walkthrough

Adds provider debugName support, refactors ProviderScope initialization to allow same-scope provider dependencies with declaration-order enforcement and forward-reference detection, renames/unifies scope errors, adds benchmarks and CI workflow, and updates docs, tests, and analysis options to match the new provider ordering semantics. (48 words)

Changes

Cohort / File(s) Summary
Analysis Configuration
\examples/auto_route/analysis_options.yaml`, `examples/bloc/analysis_options.yaml`, `examples/solidart/analysis_options.yaml``
Added analyzer error ignore specify_nonobvious_property_types to each analysis options file.
Provider debugName Feature
\packages/disco/lib/src/models/providers/provider.dart`, `packages/disco/lib/src/models/providers/provider_argument.dart`, `packages/disco/lib/src/models/overrides/provider_argument_override.dart``
Introduced optional debugName fields/parameters on Provider, ArgProvider, and ArgProviderOverride; constructors/factories updated to propagate debugName.
Debug Extensions
\packages/disco/lib/src/utils/extensions.dart``
New private extensions providing _debugName getters that format provider identifiers with type and optional name.
Disco Internal
\packages/disco/lib/src/disco_internal.dart``
Added part 'utils/extensions.dart'; to include the new extensions.
ProviderScope Refactoring
\packages/disco/lib/src/widgets/provider_scope.dart``
Major refactor: centralized _getOrCreateValue, provider/arg-provider indexing to preserve declaration order, two-phase initialization, same-scope creation tracking, forward-reference detection (ProviderForwardReferenceError), unified scope error (ProviderWithoutScopeError), renamed multiple-override error, and BuildContext-aware creation methods.
Version & Changelog
\packages/disco/pubspec.yaml`, `packages/disco/CHANGELOG.md``
Bumped package version to 2.0.0+1 and updated changelog entries for same-scope provider ordering and debugName.
Test Suite
\packages/disco/test/disco_test.dart``
Updated tests to reflect renamed errors/fields and expanded scenarios covering same-scope access, forward-reference detection, circular dependencies, provider ordering, ArgProvider interactions, and Portal behaviors.
Benchmarking
\packages/disco/benchmark/provider_benchmark.dart`, `packages/disco/.pubignore``
New benchmark test suite with stress tests and Markdown reporting; added benchmark/ to .pubignore.
Benchmark Workflow
\ .github/workflows/benchmark.yaml` ``
New GitHub Actions workflow to run benchmarks on PRs/push, upload artifacts, update README on main/dev, and comment PRs with results.
Docs
\docs/src/content/docs/core/scoped-di.mdx`, `docs/src/content/docs/miscellaneous/reactivity.md``
Replaced nested ProviderScope examples with single-scope ordering examples; added Wrong/Correct blocks and clarified ordering requirements.
Lint & Example changes
\packages/disco_lint/lib/src/assists/base/wrap_builder.dart`, `packages/disco_lint/example/pubspec.yaml`, `packages/disco/analysis_options.yaml``
Added file-level ignore for implementation_imports, switched example dependency to local path, and added document_ignores: ignore to disco analysis options.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant ProviderScope
    participant ProviderA as Provider A
    participant ProviderB as Provider B

    rect rgb(240,248,255)
    Note over ProviderScope: Phase 1 — registration & indexing
    Client->>ProviderScope: initState()
    ProviderScope->>ProviderScope: register providers, assign indices
    end

    rect rgb(240,255,240)
    Note over ProviderScope: Phase 2 — eager creation
    ProviderScope->>ProviderA: create if non-lazy
    ProviderA-->>ProviderScope: value cached
    end

    rect rgb(255,250,230)
    Note over ProviderScope: Phase 3 — lazy/on-demand creation with same-scope checks
    Client->>ProviderScope: request ProviderB
    ProviderScope->>ProviderScope: set _currentlyCreatingProviderIndex(B)
    alt A declared before B
        ProviderScope->>ProviderA: get (cached or create)
        ProviderA-->>ProviderScope: value
        ProviderScope->>ProviderB: create using ProviderA
        ProviderB-->>ProviderScope: success
        ProviderScope-->>Client: return value
    else B declared before A
        ProviderScope-->>Client: throw ProviderForwardReferenceError
    end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • Areas needing careful review:
    • packages/disco/lib/src/widgets/provider_scope.dart — initialization phases, index bookkeeping, forward-reference detection, and BuildContext-aware creation methods.
    • Propagation of debugName across ProviderArgProvider → overrides.
    • Tests in packages/disco/test/disco_test.dart to ensure expectations match renamed errors and new behaviors.
    • .github/workflows/benchmark.yaml for CI permissions, README update logic, and artifact handling.

Possibly related PRs

Suggested reviewers

  • manuel-plavsic

Poem

🐇 I hopped through scopes to make things clear,

debugName twinkles when errors appear,
I ordered my providers — no more stray threads,
Caught forward refs, and tucked worries to bed,
Two-point-oh cheers, now I nibble my creds. 🥕

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: access providers in same scope' directly and clearly describes the main feature added in this PR: enabling providers declared in the same ProviderScope to depend on each other.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/access-providers-in-same-scope

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link

codecov bot commented Dec 9, 2025

Codecov Report

❌ Patch coverage is 98.88889% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
packages/disco/lib/src/widgets/provider_scope.dart 98.86% 1 Missing ⚠️

Impacted file tree graph

@@           Coverage Diff           @@
##             main      #32   +/-   ##
=======================================
  Coverage   99.53%   99.53%           
=======================================
  Files           9        9           
  Lines         217      217           
=======================================
  Hits          216      216           
  Misses          1        1           
Files with missing lines Coverage Δ
...c/models/overrides/provider_argument_override.dart 100.00% <100.00%> (ø)
...kages/disco/lib/src/models/providers/provider.dart 100.00% <100.00%> (ø)
...co/lib/src/models/providers/provider_argument.dart 100.00% <ø> (ø)
packages/disco/lib/src/widgets/provider_scope.dart 99.31% <98.86%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@nank1ro nank1ro marked this pull request as ready for review December 9, 2025 15:17
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/disco/test/disco_test.dart (1)

171-195: Stale test description.

The test description at line 172 still references ArgProviderWithoutScopeError, but the assertion correctly uses the renamed ProviderWithoutScopeError. Update the description for consistency.

   testWidgets(
-      '''Test ProviderScope throws ArgProviderWithoutScopeError for a not found ArgProvider''',
+      '''Test ProviderScope throws ProviderWithoutScopeError for a not found ArgProvider''',
       (tester) async {
🧹 Nitpick comments (2)
packages/disco/lib/src/utils/extensions.dart (2)

3-10: Return type is unnecessarily nullable.

The getter always returns a non-null String (line 6 initializes s unconditionally), but the return type is declared as String?. This can mislead callers into thinking the value might be null.

Apply this diff to correct the return type:

 extension _DebugNameProvider<T extends Object> on Provider<T> {
   // Returns a debug name for the provider.
-  String? get _debugName {
+  String get _debugName {
     var s = 'Provider<$_valueType>';
     if (debugName != null) s += '(name: $debugName)';
     return s;
   }
 }

12-19: Same nullable return type issue.

Same as above - this getter always returns a non-null String.

 extension _DebugNameArgProvider<T extends Object, A> on ArgProvider<T, A> {
   // Returns a debug name for the provider with arguments.
-  String? get _debugName {
+  String get _debugName {
     var s = 'ArgProvider<$_valueType, $_argumentType>';
     if (debugName != null) s += '(name: $debugName)';
     return s;
   }
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c7fe89a and 11fe9ea.

📒 Files selected for processing (12)
  • examples/auto_route/analysis_options.yaml (1 hunks)
  • examples/bloc/analysis_options.yaml (1 hunks)
  • examples/solidart/analysis_options.yaml (1 hunks)
  • packages/disco/CHANGELOG.md (1 hunks)
  • packages/disco/lib/src/disco_internal.dart (1 hunks)
  • packages/disco/lib/src/models/overrides/provider_argument_override.dart (2 hunks)
  • packages/disco/lib/src/models/providers/provider.dart (3 hunks)
  • packages/disco/lib/src/models/providers/provider_argument.dart (4 hunks)
  • packages/disco/lib/src/utils/extensions.dart (1 hunks)
  • packages/disco/lib/src/widgets/provider_scope.dart (9 hunks)
  • packages/disco/pubspec.yaml (1 hunks)
  • packages/disco/test/disco_test.dart (4 hunks)
🔇 Additional comments (21)
examples/auto_route/analysis_options.yaml (1)

4-4: Configuration change to suppress analyzer warning across examples.

Adding specify_nonobvious_property_types suppression is consistent with updates to other example analysis_options.yaml files in this PR, likely due to type inference complexity in the new provider API.

examples/solidart/analysis_options.yaml (1)

6-6: Configuration change consistent with other examples.

Adding the same analyzer suppression rule to solidart example aligns with updates across examples in this PR.

examples/bloc/analysis_options.yaml (1)

1-3: Verify structural reordering intention.

Unlike the other examples, this file restructures the YAML by moving the analyzer section before the include statement. While the addition of specify_nonobvious_property_types: ignore is consistent with other examples, please verify that reordering the sections was intentional, as YAML configuration order can impact how rules are applied.

packages/disco/pubspec.yaml (1)

3-3: LGTM!

Major version bump to 2.0.0 is appropriate given the breaking API changes (error type renames) and significant new features (same-scope provider dependencies, debugName parameter).

packages/disco/lib/src/disco_internal.dart (1)

13-13: LGTM!

The new extensions.dart part directive follows the existing pattern and is logically placed among the utility parts.

packages/disco/CHANGELOG.md (1)

1-4: LGTM!

Changelog entries accurately document the new features and follow the established format.

packages/disco/lib/src/models/providers/provider.dart (3)

39-39: LGTM!

The debugName parameter is correctly added to the Provider constructor as an optional field.


50-52: LGTM!

The debugName parameter is properly propagated through the withArgument factory to ArgProvider._.


123-127: LGTM!

The debugName field declaration with documentation follows the class pattern and provides clear purpose documentation.

packages/disco/test/disco_test.dart (3)

517-571: LGTM!

Good test coverage for the ProviderScopePortal path with lazy providers accessing same-scope providers, testing the cached value return and portal context navigation.


858-912: LGTM!

Tests correctly validate:

  • Non-lazy provider can access an earlier provider in the same scope
  • ProviderForwardReferenceError is thrown when a provider references a later provider (wrong order)

1024-1067: LGTM!

Excellent test for ensuring lazy provider values are cached and reused. The creationCount assertion verifies the provider factory is only called once even when accessed by multiple dependent providers.

packages/disco/lib/src/models/providers/provider_argument.dart (4)

20-20: LGTM!

The debugName parameter is correctly added to the ArgProvider._ constructor.


40-41: LGTM!

Correctly propagating debugName to ArgProviderOverride._ ensures debug names are preserved through override chains.


50-52: LGTM!

The error type change from ArgProviderWithoutScopeError to ProviderWithoutScopeError unifies error handling across both Provider and ArgProvider, which is a cleaner API design.


85-87: LGTM!

The debugName field declaration follows the same pattern as in Provider, maintaining consistency across the codebase.

packages/disco/lib/src/models/overrides/provider_argument_override.dart (1)

7-9: LGTM!

The debugName parameter is properly added with appropriate nullability and immutability. The implementation follows the existing patterns in the codebase.

Also applies to: 25-27

packages/disco/lib/src/widgets/provider_scope.dart (4)

221-235: Static scope tracking looks correct for typical usage.

The static _currentlyInitializingScope with try/finally cleanup is appropriate for Flutter's synchronous widget initialization. The save/restore pattern in createProviderValue (lines 440-454) correctly handles lazy provider creation.


281-338: Two-phase initialization correctly separates registration from creation.

Phase 1 registers all providers and tracks indices before Phase 2 creates non-lazy values. This ensures isProviderInScope() works correctly during creation. The try/finally block properly cleans up static state.


646-658: Unified error handling for both provider types looks good.

The Object type with runtime switch pattern enables consistent error messaging for both Provider and ArgProvider. The fallback exception (line 653) provides a clear failure path if an unexpected type is encountered.


435-455: Save/restore pattern for lazy provider creation is well implemented.

The try/finally correctly saves and restores scope tracking state, enabling lazy providers to access earlier same-scope providers while maintaining proper cleanup on exceptions.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
packages/disco/lib/src/widgets/provider_scope.dart (1)

675-741: LGTM! Well-designed error types with clear messaging.

The renamed MultipleProviderOverrideOfSameInstance improves clarity. The new ProviderForwardReferenceError is particularly well-designed, providing comprehensive context (indices, provider references) and an actionable error message that explains both the problem and the solution.

Note: There's a typo in the error message at line 734 (missing closing backtick) that was already flagged in a previous review.

packages/disco/test/disco_test.dart (1)

859-1221: Excellent comprehensive test coverage for same-scope provider access.

The test suite thoroughly exercises the new same-scope provider access feature, covering:

  • Both lazy and non-lazy providers
  • Provider and ArgProvider interactions
  • Nested dependencies (A→B→C chains)
  • Value reuse/caching when multiple providers access the same earlier provider
  • Forward reference error scenarios
  • Mixed Provider and ArgProvider ordering

This coverage ensures the feature works correctly across various scenarios and properly detects ordering violations.

Note: The test at lines 1189-1221 has a description mismatch already flagged in a previous review.

🧹 Nitpick comments (1)
packages/disco/lib/src/widgets/provider_scope.dart (1)

241-411: Consider extracting helper methods to improve readability.

The initState method is quite long (~170 lines) and handles multiple responsibilities: validation, two-phase provider registration, and override processing. While functionally correct, extracting helper methods would improve maintainability and testability.

Consider refactoring into smaller methods:

void initState() {
  super.initState();
  _currentlyInitializingScope = this;
  
  try {
    if (widget.providers != null) {
      _initializeProviders(widget.providers!);
    } else if (widget.overrides != null) {
      _initializeOverrides(widget.overrides!);
    }
  } finally {
    _currentlyInitializingScope = null;
    _currentlyCreatingProviderIndex = null;
  }
}

void _initializeProviders(List<InstantiableProvider> allProviders) {
  _validateProvidersUniqueness(allProviders);
  _registerAllProviders(allProviders);
  _createNonLazyProviders(allProviders);
}

void _initializeOverrides(List<Override> overrides) {
  _processProviderOverrides(overrides);
  _processArgProviderOverrides(overrides);
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 11fe9ea and 57ff372.

📒 Files selected for processing (2)
  • packages/disco/lib/src/widgets/provider_scope.dart (9 hunks)
  • packages/disco/test/disco_test.dart (4 hunks)
🔇 Additional comments (9)
packages/disco/lib/src/widgets/provider_scope.dart (6)

31-36: LGTM! Clear documentation of ordering requirements.

The documentation clearly explains the ordering constraint for same-scope provider dependencies and references the error type that will be thrown. This will help developers understand the design decision upfront.


63-141: Good refactoring to centralize provider value creation logic.

The centralized _getOrCreateValue helper effectively reduces code duplication between Provider and ArgProvider access paths while enabling consistent same-scope dependency checking. The two-phase approach (check initializing scope, then search ancestors) is logical and well-structured.


435-455: LGTM! Proper save/restore pattern for lazy provider creation.

The save/restore pattern for initialization state correctly enables same-scope dependency checking for lazy providers. The try-finally ensures proper cleanup even if value creation throws.


474-497: LGTM! Consistent save/restore pattern for ArgProvider.

The same save/restore pattern is correctly applied to ArgProvider lazy creation, maintaining consistency with the Provider implementation.


646-658: LGTM! Improved error type with dynamic naming.

The updated error type now accepts any provider type and uses dynamic naming via the new debugName support. The switch expression cleanly handles both Provider and ArgProvider cases with appropriate fallback for unexpected types.


221-235: The static _currentlyInitializingScope is correctly handled for nested initialization. Lines 440-453 implement a proper save/restore pattern that preserves and restores the previous scope state, preventing any premature overwriting during nested initialization. Existing tests at lines 445-500 ("Test ProviderScope inside ProviderScopePortal works") and line 998 ("Nested provider dependencies work") already verify that nested scope initialization with cross-scope provider access works correctly. No changes are needed.

packages/disco/test/disco_test.dart (3)

190-191: LGTM! Correctly updated to use unified error type.

The test correctly reflects the API change from ArgProviderWithoutScopeError to the unified ProviderWithoutScopeError type with the provider field.


517-571: LGTM! Good test coverage for ProviderScopePortal with same-scope dependencies.

This test effectively covers the interaction between ProviderScopePortal, lazy providers, and same-scope dependencies. It verifies that lazy providers can access earlier providers in the same scope even when accessed through a portal context.


826-826: LGTM! Updated to match renamed error type.

Correctly updated to expect MultipleProviderOverrideOfSameInstance matching the renamed error type.

coderabbitai[bot]
coderabbitai bot previously approved these changes Dec 9, 2025
@nank1ro nank1ro self-assigned this Dec 9, 2025
@nank1ro nank1ro added the enhancement New feature or request label Dec 9, 2025
@nank1ro
Copy link
Member Author

nank1ro commented Dec 9, 2025

@manuel-plavsic let me know if you can review the PR given that I already tried to merge something similar in the past but failed.
Ignore codecov, I think it's dumb because I ignored the line for which it is failing 😅

PS: I bumped a major, reaching v2 because I want to create another PR to create a new package disco_lint that introduces the Wrap with ProviderScope lint wrapper.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
packages/disco/lib/src/widgets/provider_scope.dart (2)

462-482: Correct same-scope support for lazy providers.

The save/restore pattern for _currentlyInitializingScope and _currentlyCreatingProviderIndex is essential for nested lazy provider creation. The try-finally ensures proper restoration even if creation throws.

The save/restore pattern is duplicated in both createProviderValue (lines 467-481) and createProviderValueForArgProvider (lines 509-523). Consider extracting this pattern into a helper method to reduce duplication:

T _withProviderIndex<T>(int? index, T Function() create) {
  final savedScope = _currentlyInitializingScope;
  final savedIndex = _currentlyCreatingProviderIndex;
  try {
    _currentlyInitializingScope = this;
    _currentlyCreatingProviderIndex = index;
    return create();
  } finally {
    _currentlyInitializingScope = savedScope;
    _currentlyCreatingProviderIndex = savedIndex;
  }
}

However, since it's only used in two places, the current approach is acceptable.


442-452: Consider clearing index maps in dispose for consistency.

The dispose method clears the main hash maps but not the new index maps (_providerIndices and _argProviderIndices). While these will be garbage collected when the State object is disposed, explicitly clearing them would be more consistent with the existing cleanup pattern.

Add these lines in dispose for consistency:

@override
void dispose() {
  // dispose all the created providers
  createdProviderValues.forEach((key, value) {
    key._safeDisposeValue(value);
  });

  allArgProvidersInScope.clear();
  allProvidersInScope.clear();
  createdProviderValues.clear();
  _providerIndices.clear();
  _argProviderIndices.clear();
  super.dispose();
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 05957a1 and 012b870.

📒 Files selected for processing (1)
  • packages/disco/lib/src/widgets/provider_scope.dart (9 hunks)
🔇 Additional comments (17)
packages/disco/lib/src/widgets/provider_scope.dart (17)

31-36: Clear documentation of the ordering requirement.

The documentation clearly explains the ordering constraint and references the appropriate error type. This will help developers understand the feature and avoid forward-reference errors.


63-143: Excellent centralization of provider access logic.

The _getOrCreateValue helper elegantly handles both Provider and ArgProvider access with proper forward-reference detection. The two-step approach (check initialization scope first, then search ancestors) is well-structured and maintainable.

The ordering validation correctly prevents both forward references and self-references by checking requestedIndex >= currentIndex.


159-168: Clean refactoring to use the centralized helper.

Delegating to _getOrCreateValue eliminates duplication and ensures consistent behavior across both provider types.


191-201: Clean refactoring to use the centralized helper.

Delegating to _getOrCreateValue eliminates duplication and ensures consistent behavior across both provider types.


223-237: Well-designed tracking fields for initialization.

The combination of static scope tracking and instance-level index tracking enables robust same-scope dependency resolution while preventing circular references. The comments clearly explain each field's purpose.


243-256: Proper initialization with cleanup guarantees.

The try-finally block ensures that _currentlyInitializingScope and _currentlyCreatingProviderIndex are always cleared, even if initialization throws. This prevents stale state from affecting subsequent operations.


258-293: Thorough uniqueness validation.

The separate validation loops for Provider and ArgProvider make the logic clear and maintainable. The assertions appropriately catch configuration errors during development.


295-325: PHASE 1: Well-structured provider registration.

Registering all providers before creating any is essential for same-scope access to work correctly. The separation of concerns between registration and creation makes the initialization flow easy to follow.


327-356: PHASE 2: Correct non-lazy provider creation.

Setting and clearing _currentlyCreatingProviderIndex for each provider ensures accurate forward-reference detection. The sequential creation in declaration order is essential for the same-scope dependency feature.


358-363: Clear initialization orchestration.

The three-step initialization flow is well-organized and easy to understand.


429-439: Clear override initialization orchestration.

Separating provider and arg provider override processing keeps the logic modular and maintainable.


365-394: Robust provider override processing.

The uniqueness validation and immediate value creation for overrides is correct. The renamed error class (MultipleProviderOverrideOfSameInstance) is more specific than the old name.


396-427: Robust arg provider override processing.

Consistent with the provider override logic. Proper validation and immediate creation of override values.


501-524: Correct same-scope support for lazy arg providers.

Consistent with createProviderValue. The save/restore pattern properly handles nested lazy provider creation.


673-687: Improved error with dynamic naming.

Using the provider's debugName in error messages significantly improves debuggability. The switch expression cleanly handles both Provider and ArgProvider types.


704-716: Improved error class naming.

The shortened name MultipleProviderOverrideOfSameInstance is clearer and more concise while maintaining the same meaning. Appropriate for the v2 major version bump.


718-774: Excellent error class with actionable guidance.

The error message is exceptionally clear and helpful:

  • Explains the forward reference with specific indices
  • Describes why the constraint exists (prevent circular dependencies)
  • Provides concrete fix instructions (reorder the providers)

This will significantly improve the developer experience when encountering ordering issues.

coderabbitai[bot]
coderabbitai bot previously approved these changes Dec 9, 2025
@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Dec 9, 2025

Deploying flutter-disco with  Cloudflare Pages  Cloudflare Pages

Latest commit: 51ad98f
Status:🚫  Build failed.

View logs

@nank1ro nank1ro force-pushed the feat/access-providers-in-same-scope branch from 70a8946 to 6d02e04 Compare December 9, 2025 17:04
@nank1ro
Copy link
Member Author

nank1ro commented Dec 9, 2025

@copilot Do you think the new implementation can be improved by avoiding for loops and using maps only?
It would be nice to have a benchmark with N providers created and retrieved to correctly evaluate performance regressions or improvements. It should work for any kind of provider supported by the package, along with providers that depend on other providers.

Copy link

Copilot AI commented Dec 9, 2025

@nank1ro I've opened a new pull request, #34, to work on those changes. Once the pull request is ready, I'll request review from you.

Co-authored-by: nank1ro <60045235+nank1ro@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
@github-actions
Copy link

github-actions bot commented Dec 10, 2025

Provider Benchmark Results

Date: 2025-12-15 11:13:54 UTC

Results

Benchmark Time (ms)
Create 100 simple eager providers 187
Create 100 simple lazy providers 12
Create 50 providers with dependencies 9
Retrieve 100 lazy provider values 1
Create 100 ArgProviders 10
Access 100 providers in nested scopes 11
Complex dependency chain (30 providers) 9
Mixed lazy and eager (100 total) 10
ArgProviders with dependencies (50) 9
Large scale (500 providers) 8
Deep dependency chain (100 levels) 8
Wide dependency tree (100 dependents) 9
Multiple nested scopes (5 levels) 10

@nank1ro nank1ro marked this pull request as draft December 10, 2025 14:52
@nank1ro nank1ro removed the request for review from manuel-plavsic December 10, 2025 14:52
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/src/content/docs/miscellaneous/reactivity.md (1)

12-46: Example wiring is consistent with same-scope semantics; fix a small syntax issue and consider showing debugName

The move to a single ProviderScope with:

providers: [counterProvider, doubleCounterProvider],

is aligned with the new “declare dependent providers after their dependencies” rule and matches how doubleCounterProvider depends on counterProvider.

Two small suggestions:

  1. Missing semicolon in the example
final doubleCounterProvider = Provider((context) {
  return Computed(() => counterProvider.of(context).value * 2)
});

should have a semicolon after the Computed(...) call:

-  return Computed(() => counterProvider.of(context).value * 2)
+  return Computed(() => counterProvider.of(context).value * 2);
  1. Optional: showcase debugName

Since this PR introduces debugName, you might consider using it here to help users understand error messages:

final counterProvider = Provider(
  (context) => Signal(0),
  debugName: 'counterProvider',
);

final doubleCounterProvider = Provider(
  (context) => Computed(() => counterProvider.of(context).value * 2),
  debugName: 'doubleCounterProvider',
);
🧹 Nitpick comments (11)
packages/disco/benchmark/README.md (2)

73-74: Confusing wording: dependencies vs dependents.

Line 74 states "Order providers with dependencies after their dependents" but this seems inverted. A provider with dependencies should be declared after the providers it depends on (its dependencies), not after its dependents.

Consider rewording:

-3. **Order providers** with dependencies after their dependents
+3. **Order providers** with dependencies after the providers they depend on

149-151: Minor: Capitalize "Markdown" as a proper noun.

 1. Runs all benchmark tests
-2. Generates a markdown table with results
+2. Generates a Markdown table with results
 3. Posts results as a PR comment (for pull requests)
packages/disco/PERFORMANCE_SUMMARY.md (1)

40-40: Fragile line number reference.

Referencing specific line numbers in documentation (Lines 254-277) will quickly become outdated as the codebase evolves. Consider removing the line number reference or linking to a specific commit/tag.

-### 1. Optimized Duplicate Detection (Lines 254-277)
+### 1. Optimized Duplicate Detection
packages/disco/benchmark/provider_benchmark.dart (1)

461-476: Verification creates a new ProviderScope, not validating the benchmarked one.

The verification step pumps a completely new widget tree with a fresh ProviderScope, which creates new provider instances rather than verifying the values from the benchmarked scope. This works because the providers are deterministic, but the comment "Verify the final value is correct" is somewhat misleading—it's verifying the logic is correct, not that the benchmarked providers retained their values.

Consider adding a clarifying comment or restructuring to verify within the same widget tree:

      // Verify the final value is correct
+     // Note: This creates a new scope to verify provider logic, not the benchmarked instance
      await tester.pumpWidget(
.github/workflows/benchmark.yaml (2)

68-71: Bot comment detection could match unintended comments.

The condition comment.user.type === 'Bot' && comment.body.includes('Provider Benchmark Results') might match comments from other bots (e.g., CodeRabbit, Dependabot) if they happen to include "Provider Benchmark Results" in their body. Consider using a more specific marker.

-            const botComment = comments.data.find(comment => 
-              comment.user.type === 'Bot' && 
-              comment.body.includes('Provider Benchmark Results')
+            const BENCHMARK_MARKER = '<!-- disco-benchmark-results -->';
+            const botComment = comments.data.find(comment => 
+              comment.user.type === 'Bot' && 
+              comment.body.includes(BENCHMARK_MARKER)
             );

Then update _writeBenchmarkResults() in the benchmark file to include the marker at the start of the output.


53-59: Consider adding error handling for missing results file.

If the benchmark tests fail before writing benchmark_results.md, the workflow will fail at the "Comment PR with results" step. Consider adding a file existence check.

       - name: Comment PR with results
         if: github.event_name == 'pull_request'
         uses: actions/github-script@v7
         with:
           script: |
             const fs = require('fs');
+            const resultsPath = 'packages/disco/benchmark_results.md';
+            if (!fs.existsSync(resultsPath)) {
+              console.log('Benchmark results file not found, skipping PR comment');
+              return;
+            }
-            const results = fs.readFileSync('packages/disco/benchmark_results.md', 'utf8');
+            const results = fs.readFileSync(resultsPath, 'utf8');
packages/disco/OPTIMIZATION_NOTES.md (1)

163-163: Same terminology confusion as in README.

"Place providers with dependencies AFTER their dependents" should likely read "AFTER their dependencies" (the providers they depend on).

-1. **Order matters**: Place providers with dependencies AFTER their dependents (validated in debug mode)
+1. **Order matters**: Place providers with dependencies AFTER the providers they depend on (validated in debug mode)
packages/disco/lib/src/widgets/provider_scope.dart (3)

31-35: Clarify wording in provider ordering docs

The semantics are clear, but the sentence could be slightly polished: “Providers that depend on other providers…” (plural verb) reads better than “depends”. Also consider explicitly saying “in the same providers list of this ProviderScope” to avoid ambiguity.


373-402: Align error type for ArgProvider overrides with provider overrides

_processProviderOverrides throws MultipleProviderOverrideOfSameInstance, while _processArgProviderOverrides throws MultipleProviderOfSameInstance on duplicate overrides. That’s slightly confusing, since both code paths are dealing with overrides.

For consistency of diagnostics, consider using the override-specific error in both places:

-          if (ids.contains(id)) {
-            throw MultipleProviderOfSameInstance();
-          }
+          if (ids.contains(id)) {
+            throw MultipleProviderOverrideOfSameInstance();
+          }

(and adjust the doc comment of MultipleProviderOverrideOfSameInstance if you want it to explicitly mention arg providers as well).

Also applies to: 404-447


696-706: ProviderWithoutScopeError fallback branch could be more robust

Switching on provider and using _debugName for both Provider and ArgProvider makes the error much more readable. The default branch currently throws an Exception, though, which means toString() itself can fail if a new provider type is ever passed in.

Consider falling back to provider.runtimeType.toString() instead of throwing, so logging this error never introduces a second error:

-      _ => throw Exception('Unknown provider type ${provider.runtimeType}'),
+      _ => provider.runtimeType.toString(),

This keeps diagnostics resilient even if new provider flavours are introduced later.

docs/src/content/docs/core/scoped-di.mdx (1)

68-79: Docs accurately describe same-scope ordering; keep wording and behaviour aligned

The updated “Full example” and “Scoping correctly with context” sections clearly explain that:

  • Providers can depend on providers from ancestor scopes, and
  • Within the same ProviderScope, a dependent provider must be declared after the provider it depends on, otherwise a ProviderForwardReferenceError is thrown.

The “Wrong” vs “Correct” examples for doubleNumberPlusArgProvider vs numberProvider illustrate this nicely.

Two minor tweaks:

  1. Grammar/capitalisation
  • “Some providers might have a dependency on other providers, to handle such cases, you can:” → consider splitting into two sentences:
    “Some providers might depend on other providers. To handle such cases, you can:”
  • “to make it work, just swap the order of declaration:” → capitalise “To”.
  1. Keep docs in sync with runtime behaviour

Right now the docs state that a ProviderForwardReferenceError “will be thrown at runtime” when the order is wrong. In the implementation, that check is currently guarded by kDebugMode, so in release builds it won’t fire. Once you adjust the code so forward-reference detection runs in all build modes (see earlier comment in provider_scope.dart), this wording will be accurate; otherwise, you may want to qualify this as “in debug mode” to avoid confusion.

Also applies to: 86-114

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 012b870 and 083f710.

⛔ Files ignored due to path filters (1)
  • docs/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (8)
  • .github/workflows/benchmark.yaml (1 hunks)
  • docs/src/content/docs/core/scoped-di.mdx (2 hunks)
  • docs/src/content/docs/miscellaneous/reactivity.md (1 hunks)
  • packages/disco/OPTIMIZATION_NOTES.md (1 hunks)
  • packages/disco/PERFORMANCE_SUMMARY.md (1 hunks)
  • packages/disco/benchmark/README.md (1 hunks)
  • packages/disco/benchmark/provider_benchmark.dart (1 hunks)
  • packages/disco/lib/src/widgets/provider_scope.dart (12 hunks)
🧰 Additional context used
🪛 LanguageTool
packages/disco/OPTIMIZATION_NOTES.md

[style] ~196-~196: It might be better to use ‘time’ with the time-relative pronoun ‘when’. (Alternatively, use ‘in/on which’.)
Context: ...: Deep scope nesting causes O(d) lookup time where d is depth Current: Each scope look...

(WHEN_WHERE)

packages/disco/benchmark/README.md

[uncategorized] ~150-~150: Did you mean the formatting language “Markdown” (= proper noun)?
Context: ...Runs all benchmark tests 2. Generates a markdown table with results 3. Posts results as ...

(MARKDOWN_NNP)

🔇 Additional comments (10)
packages/disco/PERFORMANCE_SUMMARY.md (1)

1-132: LGTM!

The performance summary provides a clear and well-organized explanation of the optimizations made. The before/after comparisons with complexity analysis are helpful for understanding the changes.

packages/disco/benchmark/provider_benchmark.dart (3)

560-603: LGTM!

The _writeBenchmarkResults function is well-structured, generates a clear Markdown report with proper ordering, and handles missing results gracefully with 'N/A'.


1-54: LGTM!

Good use of Stopwatch for timing, proper test structure with testWidgets, and clear benchmark naming conventions. The debug mode disclaimer in the generated report is appropriate.


519-556: The test is correctly structured. Reusing the same providers list across nested scopes does not trigger duplicate provider detection—each scope validates its own provider list independently, and since the list contains 20 unique Provider instances with no duplicates, validation passes for all scopes. This approach is valid for benchmarking nested scope overhead.

Likely an incorrect or invalid review comment.

.github/workflows/benchmark.yaml (1)

1-45: LGTM!

The workflow is well-structured with appropriate triggers, caching for Flutter, and correct working directory configuration. The path filters ensure benchmarks only run when relevant code changes.

packages/disco/OPTIMIZATION_NOTES.md (2)

276-320: Good risk assessment on Provider Pooling.

The documentation correctly identifies the dangers of provider pooling (breaks scope isolation, testing complexity, lifecycle issues) and marks it as high-risk. This honest assessment helps future maintainers understand why this optimization was not pursued.


1-425: LGTM!

Excellent technical documentation. The three-phase initialization explanation, complexity analysis, and future optimization proposals with honest trade-off assessments provide valuable context for maintainers. The section on parallel initialization correctly identifies Flutter's synchronous widget constraints.

packages/disco/lib/src/widgets/provider_scope.dart (3)

263-285: Uniqueness check forbids multiple InstantiableArgProviders for the same ArgProvider

_validateProvidersUniqueness de-duplicates InstantiableArgProvider entries by item._argProvider, not by the instantiable itself. This means a providers list like:

ProviderScope(
  providers: [
    doubleNumberPlusArgProvider(10),
    doubleNumberPlusArgProvider(20),
  ],
  child: ...
)

will throw MultipleProviderOfSameInstance in debug builds, even though the two entries differ by argument.

Is this intentional? If you want to support scoping the same ArgProvider with multiple arguments in a single scope, this assertion should distinguish instances (or include the argument) rather than the shared _argProvider. If you intend to forbid that pattern, it might be worth calling it out explicitly in the docs.


565-573: Coverage ignores around debug-only diagnostics are fine

Marking debugFillProperties, updateShouldNotify, and updateShouldNotifyDependent with // coverage:ignore-* is reasonable here, since they’re deterministic and not critical to behavioural correctness. No changes needed.

Also applies to: 582-587, 603-611


727-739: New override and forward-reference error types look good

MultipleProviderOverrideOfSameInstance and ProviderForwardReferenceError are well named, and the ProviderForwardReferenceError message is clear about what went wrong and how to fix it (move $requestedName before $currentName in the list).

Once the forward-reference detection is made to run in release as well (see earlier comment), these errors should be very helpful for diagnosing mis-ordered providers.

Also applies to: 741-786

@nank1ro nank1ro marked this pull request as ready for review December 11, 2025 11:53
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/disco/test/disco_test.dart (1)

811-827: Test description still references old error name

The test description still says “MultipleProviderOverrideOfSameProviderInstance” while the expectation now uses MultipleProviderOverrideOfSameInstance. Functionally the test is correct, but the wording is slightly stale.

You can align the description with the new type name:

-  testWidgets(
-      '''ProviderScopeOverride must throw a MultipleProviderOverrideOfSameProviderInstance for duplicated providers''',
+  testWidgets(
+      '''ProviderScopeOverride must throw a MultipleProviderOverrideOfSameInstance for duplicated providers''',
       (tester) async {
🧹 Nitpick comments (4)
.github/workflows/benchmark.yaml (1)

52-68: README update logic assumes Benchmark is the final section.

The sed command (line 61) removes everything from "## Benchmark" to the end of file. If other sections are added after the Benchmark section in the future, they will be accidentally deleted. Consider a more defensive approach.

For robustness, replace the sed-based deletion with explicit section replacement:

       - name: Update README with benchmark results
         if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev')
         run: |
           # Read benchmark results
           BENCHMARK_CONTENT=$(cat packages/disco/benchmark_results.md)

           # Check if README already has a Benchmark section
           if grep -q "^## Benchmark" packages/disco/README.md; then
             # Remove everything from ## Benchmark to the end
-            sed -i '/^## Benchmark/,$d' packages/disco/README.md
+            # More robust: extract everything before ## Benchmark
+            sed -i '/^## Benchmark/,$d' packages/disco/README.md
           fi

           # Add benchmark section at the end
           echo "" >> packages/disco/README.md
           echo "## Benchmark" >> packages/disco/README.md
           echo "" >> packages/disco/README.md
           echo "$BENCHMARK_CONTENT" >> packages/disco/README.md

Alternatively, use a marker-based approach if other sections may follow Benchmark:

sed -i '/^## Benchmark/,/^## [^B]/d; /^## Benchmark/d' packages/disco/README.md
packages/disco/lib/src/widgets/provider_scope.dart (3)

63-133: Same‑scope creation + forward‑reference detection logic looks correct; note behaviour for overrides

The combination of:

  • _getOrCreateValue’s same‑scope branch,
  • _currentlyInitializingScope / _currentlyCreatingProviderIndex / _currentlyCreatingProvider,
  • index maps populated in _registerAllProviders, and
  • the wrapping in createProviderValue / createProviderValueForArgProvider

gives a coherent model:

  • Forward references in the same ProviderScope (requested index ≥ current index) reliably throw ProviderForwardReferenceError in both init and lazy creation.
  • Earlier providers (including lazy ones) in the same scope are created on demand and reused via createdProviderValues, with static state correctly saved/restored through nested calls and cross‑scope access.
  • Cross‑scope reads skip the same‑scope branch via isInScope and fall back to the usual ancestor/portal lookup.

One nuance: overrides initialized via _initializeOverrides don’t populate _providerIndices / _argProviderIndices or set _currentlyCreatingProviderIndex, so forward‑reference detection and “same‑scope” semantics effectively do not apply within override lists. That’s perfectly safe, but it’s an implicit difference from providers; if you ever want overrides to participate in the same ordering rules, you’d need a similar “phase 1 register + phase 2 create with index tracking” flow there too.

Also applies to: 213-253, 279-351, 450-518


255-277: Duplicate‑provider checks are assert‑only; confirm that dev‑only enforcement is desired

_validateProvidersUniqueness, _processProviderOverrides, and _processArgProviderOverrides all throw MultipleProviderOfSameInstance / MultipleProviderOverrideOfSameInstance only from inside assert blocks. This mirrors the existing pattern, but means:

  • In debug/tests: duplicates throw as expected.
  • In release: duplicates are silently allowed, and later entries just overwrite earlier ones in the maps.

If the intent is to treat duplicates as programmer errors that must never reach production, the current design is fine. If you want consistent runtime behaviour between debug and release, consider moving the checks out of assert or duplicating them in non‑assert code.

Also applies to: 353-427


659-681: Error type refinements and messages align with new semantics

  • ProviderWithoutScopeError now takes a generic Object provider and derives a debug name for both Provider and ArgProvider, which matches the updated tests and makes messages clearer.
  • MultipleProviderOverrideOfSameInstance cleanly distinguishes override duplication from base provider duplication.
  • ProviderForwardReferenceError carries both currentProvider and requestedProvider and formats them via _debugName, producing a concise, actionable error that aligns with the declaration‑order rules.

The defensive switch fallbacks that throw for unknown provider types should be unreachable given the current API surface; if you later introduce new provider‐like types, you might want to either broaden the pattern or fall back to runtimeType instead of throwing from toString, but as written this is acceptable for an “impossible” branch.

Also applies to: 698-710, 712-757

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 083f710 and d3f6751.

📒 Files selected for processing (5)
  • .github/workflows/benchmark.yaml (1 hunks)
  • packages/disco/.pubignore (1 hunks)
  • packages/disco/benchmark/provider_benchmark.dart (1 hunks)
  • packages/disco/lib/src/widgets/provider_scope.dart (12 hunks)
  • packages/disco/test/disco_test.dart (5 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/disco/.pubignore
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/disco/benchmark/provider_benchmark.dart
🔇 Additional comments (6)
.github/workflows/benchmark.yaml (1)

83-119: GitHub Script for PR comments is well-structured.

The logic correctly detects existing bot comments by checking for both Bot user type and the comment text identifier. Updating vs. creating comments prevents duplication and keeps the PR clean.

packages/disco/lib/src/widgets/provider_scope.dart (2)

31-36: Doc comment clearly communicates ordering contract

The added documentation on providers explicitly states the declaration-order constraint and the ProviderForwardReferenceError behaviour; this matches the implementation and will help users avoid subtle bugs.


536-544: Coverage ignores around debug‑only InheritedWidget hooks are reasonable

Marking debugFillProperties, updateShouldNotify, and updateShouldNotifyDependent with coverage ignores keeps noise out of coverage reporting while still exercising the main provider logic elsewhere. No functional concerns here.

Also applies to: 553-583

packages/disco/test/disco_test.dart (3)

171-195: Updated ArgProvider “not found” test matches new error API

The test now correctly expects ProviderWithoutScopeError and inspects the unified .provider field, asserting it equals the ArgProvider instance. This aligns with the refactored error type and ensures ArgProviders are covered by the same error path as regular Providers.


517-571: Portal + same‑scope lazy provider test exercises the new common path

The ProviderScopePortal with lazy providers accessing same‑scope providers test nicely validates that:

  • Lazy providers declared in the same ProviderScope can depend on each other via declaration order, and
  • Access through ProviderScopePortal still hits the same _getOrCreateValue path (including caching and same‑scope semantics).

This is a good regression test for the portal + same‑scope integration.


859-1305: New same‑scope, forward‑reference, caching, and circular‑dependency tests provide strong coverage

The added block of tests around:

  • same‑scope access for combinations of lazy/non‑lazy Providers and ArgProviders,
  • forward references in both directions (Provider→Provider, ArgProvider→Provider, Provider→ArgProvider, ArgProvider→ArgProvider),
  • reuse of previously created values when multiple dependents access the same earlier provider, and
  • “impossible by design” circular dependencies that surface as ProviderForwardReferenceError

gives very thorough coverage of the new ordering semantics. The scenarios mirror the implementation details (index maps, initialization vs lazy creation, mixed Provider/ArgProvider lists) and should catch regressions in _getOrCreateValue or the index bookkeeping.

No functional issues spotted; this suite looks well designed for the new behaviour.

coderabbitai[bot]
coderabbitai bot previously approved these changes Dec 11, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
packages/disco/test/disco_test.dart (1)

1189-1221: Later ArgProvider forward-reference test now matches error type

The test name and expectation both target ProviderForwardReferenceError, addressing the previously noted mismatch between description and asserted type.

🧹 Nitpick comments (2)
packages/disco/test/disco_test.dart (2)

517-571: Portal + lazy same-scope test exercises the right code paths

This test nicely covers ProviderScopePortal with lazy providers that depend on an earlier same-scope provider, ensuring the portal path and cached-value resolution behave as expected. One minor readability nit: consider renaming the local double variable to something like doubleValue to avoid shadowing the double type.


859-1187: Strong coverage of same-scope ordering, laziness, ArgProviders, and caching

This block of tests thoroughly exercises the new semantics: non-lazy vs lazy combinations, Provider↔ArgProvider interactions, multi-step chains (A→B→C), and reuse of a single created value when multiple providers read from the same earlier provider. It should give good regression protection around same-scope access and caching behaviour. If you ever want to reduce duplication, many of these MaterialApp/Scaffold/ProviderScope+Builder scaffolds could be factored into a small helper, but that’s optional.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d3f6751 and 80595ad.

📒 Files selected for processing (2)
  • docs/src/content/docs/miscellaneous/reactivity.md (1 hunks)
  • packages/disco/test/disco_test.dart (6 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (4)
docs/src/content/docs/miscellaneous/reactivity.md (1)

16-25: Example now matches same-scope provider semantics

Registering both counterProvider and doubleCounterProvider in a single ProviderScope in dependency order, along with the fixed Computed return line, correctly illustrates the new same-scope access model and forward-reference behaviour.

packages/disco/test/disco_test.dart (3)

171-195: ArgProvider not-found test now matches unified ProviderWithoutScopeError API

The description, TypeMatcher<ProviderWithoutScopeError>, and use of error.provider are consistent and aligned with the non-Arg provider case, which keeps the error surface coherent.


811-827: Override duplication test correctly targets MultipleProviderOverrideOfSameInstance

The test description and expectation now both use MultipleProviderOverrideOfSameInstance, clearly separating override-duplication failures from MultipleProviderOfSameInstance in regular scopes.


1223-1305: Circular-dependency prevention tests clearly encode the forward-reference invariant

Both scenarios (Provider→Provider and Provider→ArgProvider) demonstrate that any would-be cycle in a single scope must hit a forward reference first and thus raise ProviderForwardReferenceError, which captures the intended design constraint and helps guard against regressions.

coderabbitai[bot]
coderabbitai bot previously approved these changes Dec 11, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/disco_lint/lib/src/assists/base/wrap_builder.dart (1)

1-8: Implementation imports from internal analyzer APIs create maintenance risk.

The code imports three internal APIs from analyzer's /src/ paths (lines 5-7):

  • analyzer/src/dart/ast/extensions.dart
  • analyzer/src/dart/element/type.dart
  • analyzer/src/utilities/extensions/flutter.dart

While the analyzer: ^8.0.0 constraint in pubspec.yaml prevents major version breakage, it does not protect against breaking changes in minor versions. The analyzer package is known to make breaking changes within minor versions without notice, creating fragility.

Add inline comments explaining why each implementation import is necessary and what public analyzer or analyzer_plugin API would serve as an alternative if available. Consider pinning analyzer more tightly (e.g., ^8.1.0 or 8.1.x) if targeting specific known-stable versions.

🧹 Nitpick comments (1)
packages/disco/analysis_options.yaml (1)

14-16: Reconsider suppressing documentation warnings for a major version release.

Adding document_ignores: ignore silences all documentation-related analyzer warnings globally. For a major version bump (v2) that introduces significant API changes (debugName parameter, provider ordering semantics), this risks masking real documentation gaps that should be addressed—especially for public APIs that consumers depend on.

If specific files legitimately lack documentation by design, prefer using file-level ignore comments (e.g., // ignore: document_ignores or /// ignore: document_ignores) rather than project-wide suppression.

Please clarify:

  • Why is this suppression necessary for the v2 release?
  • Are there intentionally undocumented public APIs, or should these be documented instead?
  • If suppression is needed, would file-level ignores be more appropriate than project-wide?
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 80595ad and 30dba33.

📒 Files selected for processing (7)
  • examples/solidart/analysis_options.yaml (1 hunks)
  • packages/disco/CHANGELOG.md (1 hunks)
  • packages/disco/analysis_options.yaml (1 hunks)
  • packages/disco/pubspec.yaml (1 hunks)
  • packages/disco/test/disco_test.dart (17 hunks)
  • packages/disco_lint/example/pubspec.yaml (1 hunks)
  • packages/disco_lint/lib/src/assists/base/wrap_builder.dart (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/disco/CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/disco/pubspec.yaml
  • examples/solidart/analysis_options.yaml
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Flutter test
  • GitHub Check: Run Provider Benchmarks
🔇 Additional comments (8)
packages/disco_lint/example/pubspec.yaml (1)

14-15: LGTM!

The switch to a local path dependency aligns with the workspace setup and is the appropriate pattern for an example project during development. This allows the example to test against the latest local disco package changes.

packages/disco/test/disco_test.dart (6)

9-9: LGTM! Appropriate lint suppression.

The unreachable_from_main ignore is correct since NameContainer is only used in test code.


167-172: LGTM! Error type and field updates are consistent.

The renamed error types (ProviderWithoutScopeError, MultipleProviderOfSameInstance) and unified field access (.provider) are correctly applied throughout the test suite, matching the PR's public API changes.

Also applies to: 175-201, 203-228


385-385: LGTM! Proper future handling in tests.

Using .ignore() on unawaited showDialog futures is the correct approach to suppress the unawaited_futures lint in test scenarios where the dialog result is not needed.

Also applies to: 441-441, 515-515, 573-573


885-1251: Excellent comprehensive test coverage for same-scope provider access!

This test suite thoroughly validates the new same-scope provider dependency feature:

  • Tests all combinations of lazy/non-lazy providers
  • Correctly validates forward-reference detection
  • Verifies value caching when multiple providers access the same dependency
  • Tests nested dependency chains (A→B→C)
  • Covers both Provider and ArgProvider scenarios

The test logic and expectations are correct throughout. This provides strong confidence in the feature's correctness.


1253-1339: Excellent demonstration of circular dependency prevention by design!

These tests effectively validate that the declaration-order enforcement makes circular dependencies impossible within the same scope. Any attempt to create circular dependencies results in ProviderForwardReferenceError, which aligns perfectly with the PR's stated goal of "preventing circular dependencies by design."

The test comments clearly explain the prevention mechanism, making this a valuable documentation reference.


536-589: LGTM! Important edge case coverage.

This test validates that same-scope provider dependencies work correctly through ProviderScopePortal, including proper context navigation and cached value retrieval. This is an important validation of the feature's robustness across different widget tree patterns.

packages/disco_lint/lib/src/assists/base/wrap_builder.dart (1)

10-84: LGTM!

The WrapBuilder abstraction is well-designed with proper null safety, clean separation of concerns, and correct indentation handling for generated code. The use of early returns keeps the logic clear, and the builder pattern integration with analyzer_plugin APIs is implemented correctly.

@manuel-plavsic
Copy link
Member

I'll have time to review it this week. Probably tomorrow or on Thursday ;)

Copy link
Member

@manuel-plavsic manuel-plavsic left a comment

Choose a reason for hiding this comment

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

I am still not finishing reviewing. I still need to review the main part of the provider_scope.dart file. I will do it ASAP.

I like the idea of a user-friendlier API 👍

Comment on lines +92 to 93
The provider `doubleNumberPlusArgProvider` depends on `numberProvider`. Therefore, when scoping them both in the same `ProviderScope`, `numberProvider` must be declared first.

Copy link
Member

Choose a reason for hiding this comment

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

after "... must be declared first." Add: "Therefore, the following code will lead to a ProviderWithoutScopeError:"

and then you enter the wrong example.

```

Placing the `ProviderScope` containing `doubleNumberPlusArgProvider` above the one containing `numberProvider` would also not work. It needs to be like in the full example above.
to make it work, just swap the order of declaration:
Copy link
Member

Choose a reason for hiding this comment

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

I would change this line to : "The error can be prevented by reordering the list of providers:"

analyzer:
errors:
always_put_required_named_parameters_first: ignore
specify_nonobvious_property_types: ignore
Copy link
Member

Choose a reason for hiding this comment

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

Good catch! Disco is very neat, most of the time the types will be inferred correctly ;).

The only reason to specify a different type is when working with sub-classes, but in that case one would have to do it anyway.

Maybe we can recommend setting this rule somewhere in the docs too?

@@ -1,5 +1,7 @@
## 2.0.0

- **FEAT**: Allow providers declared in the same `ProviderScope` to depend on each other, by leveraging the order of declaration. This simplifies the development experience and prevents circular dependencies by design.
Copy link
Member

Choose a reason for hiding this comment

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

Two things:

  • "to depend on each other" this is wrong. We are just letting the ones defined below to depend on the ones defined above.
  • Circular dependencies were actually never a thing. Also, the screenshot you uploaded in the PR does not feature a circular dependency, but the prevention mechanism.

So, I think this point should be reframed to:

"Allow providers in the same ProviderScope to depend on previously declared providers. This simplifies the development experience. This friendlier syntax does not introduce circular dependencies."

Comment on lines +14 to +16
analyzer:
errors:
document_ignores: ignore
Copy link
Member

Choose a reason for hiding this comment

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

I don't see the point of adding this? Can you should me an example of its usefulness?

Comment on lines +31 to 36
///
/// Providers can be either [Provider] or [ArgProvider].
/// Providers that depends on other providers in the same [providers] list
/// must be listed after the providers they depend on, otherwise a
/// [ProviderForwardReferenceError] will be thrown.
final List<InstantiableProvider>? providers;
Copy link
Member

Choose a reason for hiding this comment

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

Is this the right place for these comments?

Also, an InstantiableProvider can be either a Provider or an InstantiableArgProvider (i.e. an ArgProvider with its argument). The comment says it can be either a Provider or an ArgProvider. It is wrong and misleading.

I overall think the comments might just add confusion. I would remove them and keep the same docstring as before.


/// Track the scope currently being initialized. This enables same-scope
/// provider access during initialization.
static ProviderScopeState? _currentlyInitializingScope;
Copy link
Member

Choose a reason for hiding this comment

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

I guess this can be solved like this... I am just a bit unsure. Are we basically using global state for the current scope?

Comment on lines 449 to 473
/// Creates a provider value and stores it to [createdProviderValues].
dynamic createProviderValue(Provider id) {
dynamic createProviderValue(Provider id, BuildContext context) {
// find the intermediate provider in the list
final provider = getIntermediateProvider(id)!;
// create and return its value
final value = provider._createValue(context);
// store the created provider value
createdProviderValues[id] = value;
return value;

// Support same-scope access for lazy providers
final savedScope = _currentlyInitializingScope;
final savedIndex = _currentlyCreatingProviderIndex;
final savedProvider = _currentlyCreatingProvider;
try {
_currentlyInitializingScope = this;
_currentlyCreatingProviderIndex = _providerIndices[id];
_currentlyCreatingProvider = id;

// create and return its value
final value = provider._createValue(context);
// store the created provider value
createdProviderValues[id] = value;
return value;
} finally {
_currentlyInitializingScope = savedScope;
_currentlyCreatingProviderIndex = savedIndex;
_currentlyCreatingProvider = savedProvider;
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Add some comments here, such as:

  /// Creates a provider value and stores it to [createdProviderValues].
  dynamic createProviderValueForArgProvider(
    ArgProvider id,
    BuildContext context,
  ) {
    // Find the intermediate provider in the list
    final provider = getIntermediateProviderForArgProvider(id)!;

    // Temporarily override shared creation state
    final savedScope = _currentlyInitializingScope;
    final savedIndex = _currentlyCreatingProviderIndex;
    final savedProvider = _currentlyCreatingProvider;

    try {
      _currentlyInitializingScope = this;
      _currentlyCreatingProviderIndex = _argProviderIndices[id];
      _currentlyCreatingProvider = id;

      // Create the provider value (may throw or trigger nested creation)
      final value = provider._createValue(context);

      // Store the created provider value
      createdProviderValues[allArgProvidersInScope[id]!] = value;

      return value; // finally still runs after this return
    } finally {
      // Restore shared state on both success and failure
      _currentlyInitializingScope = savedScope;
      _currentlyCreatingProviderIndex = savedIndex;
      _currentlyCreatingProvider = savedProvider;
    }
  }

Comment on lines 491 to 518
/// Creates a provider value and stores it to [createdProviderValues].
dynamic createProviderValueForArgProvider(
ArgProvider id,
BuildContext context,
) {
// find the intermediate provider in the list
final provider = getIntermediateProviderForArgProvider(id)!;
// create and return its value
final value = provider._createValue(context);
// store the created provider value
createdProviderValues[allArgProvidersInScope[id]!] = value;
return value;

// Support same-scope access for lazy providers
final savedScope = _currentlyInitializingScope;
final savedIndex = _currentlyCreatingProviderIndex;
final savedProvider = _currentlyCreatingProvider;
try {
_currentlyInitializingScope = this;
_currentlyCreatingProviderIndex = _argProviderIndices[id];
_currentlyCreatingProvider = id;

// create and return its value
final value = provider._createValue(context);
// store the created provider value
createdProviderValues[allArgProvidersInScope[id]!] = value;
return value;
} finally {
_currentlyInitializingScope = savedScope;
_currentlyCreatingProviderIndex = savedIndex;
_currentlyCreatingProvider = savedProvider;
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Add some comments here too

Copy link
Member

@manuel-plavsic manuel-plavsic left a comment

Choose a reason for hiding this comment

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

There are a few things to adjust, bit it LGTM otherwise. The core logic seems sound. Good job!

/// access during initialization and lazy creation.
static T? _getOrCreateValue<T extends Object, ID extends Object>({
required BuildContext context,
required ID id,
Copy link
Member

Choose a reason for hiding this comment

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

Write that ID is either Provider<T> or ArgProvider<T, A> in the function's docstring. Makes the code immediately more understandable.

Comment on lines +217 to +224
/// Map each provider to its index in the original providers list.
/// Used to enforce ordering constraints during same-scope access.
final HashMap<Provider, int> _providerIndices = HashMap<Provider, int>();

/// Map each ArgProvider to its index in the original providers list.
/// Used to enforce ordering constraints during same-scope access.
final HashMap<ArgProvider, int> _argProviderIndices =
HashMap<ArgProvider, int>();
Copy link
Member

Choose a reason for hiding this comment

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

We can omit the type here thanks to type inference.

  /// Map each provider to its index in the original providers list.
  /// Used to enforce ordering constraints during same-scope access.
  final _providerIndices = HashMap<Provider, int>();

  /// Map each ArgProvider to its index in the original providers list.
  /// Used to enforce ordering constraints during same-scope access.
  final _argProviderIndices = HashMap<ArgProvider, int>();

Comment on lines +242 to +247
try {
if (widget.providers != null) {
_initializeProviders(widget.providers!);
} else if (widget.overrides != null) {
_initializeOverrides(widget.overrides!);
}
Copy link
Member

Choose a reason for hiding this comment

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

I am a bit rusty with our code base. Is it correct that we give precedence to providers over overrides here?

Comment on lines 302 to 306
final provider =
instantiableArgProvider._argProvider._generateIntermediateProvider(
instantiableArgProvider._arg,
);
allArgProvidersInScope[id] = provider;
Copy link
Member

Choose a reason for hiding this comment

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

I know this is some old code, but VSCode is formatting it differently.

Screenshot_2025-12-26_18-15-05

What is going on? Can you format the file and tell me if the code looks like in this PR or like in my screenshot?

Copy link
Member

Choose a reason for hiding this comment

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

not only here, but throughout the entire code base

Comment on lines +107 to +109
// coverage:ignore-start
if (createdProvider != null) return createdProvider as T;
// coverage:ignore-end
Copy link
Member

Choose a reason for hiding this comment

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

Also here, VSCode formats it like this in my case:

Screenshot_2025-12-26_18-18-18

It could also be that I am doing something wrong.


/// Helper method to handle common logic between Provider and ArgProvider
/// access during initialization and lazy creation.
static T? _getOrCreateValue<T extends Object, ID extends Object>({
Copy link
Member

@manuel-plavsic manuel-plavsic Dec 26, 2025

Choose a reason for hiding this comment

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

This is the core function of this PR. I inspected it, and it seems fine to me. It uses global state to track the current instantiating ProviderScope, but it seems to be pretty safe IMO, since there can only be one instantiating ProviderScope at a time thanks to the event loop. I don't see a better or more efficient alternative here. Good job!

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

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants