Repository navigation
Adds styleguide change proposal for package private #30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
22f956f
37fccd0
1f0f3f5
bb70f0c
17077d6
f2eacc7
cb557b6
09948cc
39649c5
4c83c57
f40f447
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,208 @@ | ||
| --- | ||
| type: rfc | ||
| rfc: '710.0000' | ||
| title: Style guide changes for package private | ||
| description: Updates the Flutter style guide and breaking change policy for package-private APIs and @internal. | ||
| status: draft | ||
| created: 2026-10-06T00:00:00Z | ||
| updated: 2026-11-02T18:30:00Z | ||
| tags: | ||
| - 710-style-guide | ||
| - 030-breaking-changes | ||
| authors: | ||
| - https://github.com/chunhtai | ||
| --- | ||
|
|
||
| # RFC 710.0000: Style guide changes for package private | ||
|
|
||
| ## Overview | ||
|
|
||
| This document proposes updating the [Flutter repository style guide](https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md) and [Flutter breaking change policy](https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes) to discourage rather than ban package-private APIs across `flutter/flutter` and `flutter/packages`, and to adopt `@internal` when package-private APIs are needed. | ||
|
|
||
| Declarations in `lib/src/` that are not exported by public barrel files (`lib/<layer>.dart`) and are marked `@internal` fall outside the public API contract; modifying or removing them is not a breaking change. | ||
|
|
||
| Package-private APIs are permitted only for: | ||
|
|
||
| * Web and I/O conditional imports. | ||
| * Experimental APIs that are annotated with `@internal` and gated behind a feature flag. | ||
| * Internal-facing APIs shared across files within the same layer, when library-private declarations are impractical. | ||
|
LongCatIsLooong marked this conversation as resolved.
Resolved
|
||
|
|
||
| ## Context | ||
|
|
||
| The style guide bans package-private APIs beyond library-private (`_`) declarations and requires every file in `lib/src/<layer>/` to be exported by `lib/<layer>.dart`. When an implementation detail is needed across multiple files within a layer, contributors are left with two poor options: | ||
|
|
||
| * Keep declarations library-private (`_`), which forces all related code into a single large file. | ||
| * Expose implementation details as public APIs (such as `NavigatorState.finalizeRoute` shared between `navigator.dart` and `routes.dart`, or `HardwareKeyboard.syncKeyboardState` shared between `hardware_keyboard.dart` and `binding.dart` in `services`), which commits the framework to backward compatibility and discourages refactoring. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I feel this would encourage the framework to sit on tech debts instead, if If the API design is leaky and it's a real framework tech debt (the more common case), wouldn't improving the API and deprecating the existing leaky API be the more correct thing to do (especially if you're already refactoring it)? If the API design is leaky because it has to be leaky (e.g., because of a language limitation), then I feel it's justified to tell API users that the API isn't really a part of the public contract and should not be used (and prevent dartdoc from generating a reference for that API).
I think it's still part of the
Not familiar with this one, but Gemini suggested the leak is avoidable if we redesign the API if we pass the initial key presses synchronously. So technically a tech debt? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we require a TODO comment for
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
they can do that by calling super, I think the reason is didpop can have animation, but that is self contained in TransitionRoute. One arguement is someone implement route interface, but it won't work without significant hack in navigator because there is internal logic in base route class. The only use case I can think of that can't be solve by inheritance is that they implement on top of Route but below TransitionRoute, It will break a bunch of other stuff like gesture pop and predicative back. So I don't think that is something we should support. I agree this may be able to be prevented through less leaky design, but that will mean we will build transition and animation in mind when designing navigator, or just move TransitionRoute to navigator.dart. but that back to the question whether we are going to merge classes and different concepts in a single file or not.
I think sometimes this is just unavoidable, and the alternative is big files. but I think requiring a comment reasoning may be a good practice |
||
|
|
||
| In practice, the framework already omits `_`-prefixed files in `lib/src/` from barrel exports (such as `lib/src/widgets/_accessibility_evaluations.dart`, `lib/src/widgets/_window.dart`, and `lib/src/foundation/_features.dart`), and `flutter/packages` already uses `@internal` with `hide` (such as `GoRouterStateRegistry` in `go_router`). | ||
|
|
||
| Relying on filename conventions alone risks accidental leaks. Because `lib/src/` files are re-exported by default, moving code or sharing a non-private class within a layer can inadvertently expose it to external consumers, making future edits breaking changes. Marking these declarations `@internal` triggers an [`invalid_export_of_internal_element`](https://dart.dev/tools/diagnostics/invalid_export_of_internal_element) analyzer error if exported from a public barrel file, and triggers [`invalid_use_of_internal_member`](https://dart.dev/tools/diagnostics/invalid_use_of_internal_member) if external consumers import `lib/src/` directly. | ||
|
|
||
| ## Proposal | ||
|
|
||
| ### Breaking change policy and customer tests | ||
|
|
||
| * Modifying, renaming, or removing any declaration in `lib/src/` that is not exported by a public barrel file and is annotated with `@internal` is not a breaking change and requires no deprecation period or announcement, subject to the grace period below. | ||
| * Adding `@internal` to an existing declaration must be accompanied by a comment that ends with `This feature was made internal after v<version>.`, where `<version>` is the latest Flutter version tag when the PR is written, following the same convention as `@Deprecated` messages (for example, `// This feature was made internal after v3.50.0-0.3.pre.`). Until a stable release that includes the annotation has shipped, changes to the declaration follow the normal breaking change policy. Declarations that are `@internal` from the start have no grace period. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This does not grant much of a grace period. Since a change on What's worse, we cannot keep the symbol exported in the barrel file during this grace period: adding Combining my suggestions in #30 (comment), the process for making an existing public symbol
|
||
| * Registered tests in [`flutter/tests`](https://github.com/flutter/tests) must not import `package:flutter/src/` or depend on `@internal` APIs. Breakages in customer tests caused by `package:flutter/src/` imports or `@internal` usage are not breaking changes, and contributors may disable or update offending customer tests when modifying `@internal` declarations that are past any grace period. | ||
|
|
||
| ### Package-private APIs (`@internal`) | ||
|
|
||
| Package-private APIs remain discouraged in general, rather than banned. Unexported files and package-private declarations in `lib/src/` are permitted only in the following cases: | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I updated the wording to discourage use of package private instead of banning. |
||
|
|
||
| * Experimental APIs that are not yet ready for public stability guarantees. These APIs must be both annotated with `@internal` and gated behind a feature flag. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I again suggest keeping experimental APIs out of this RFC, as I mentioned earlier. How experimental APIs should work is more than just a tag, and hasn't been settled in this thread:
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree that The only existing case, windowing, doesn't follow the other rules in this RFC: Since windowing is the only experimental API using this pattern today, deferring the category to a separate RFC costs little. If you'd rather keep it here, I'd at least like the RFC to cover how windowing conforms, how users are expected to opt in, and what graduation looks like.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the current rule in this rfc should have covered this? that the windowingOwner should have been internal extension and should be fixed.
External package or app should not use it so there is no opt in for them even in their tests. Their only channel is to file an issue to open the API to public If you meant the test in flutter/flutter, I am less concern about how they get around the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moving More importantly, "external apps should not use it" contradicts why the API is behind a user-facing Therefore applying Speaking more broadly, both issues come from the same root cause: |
||
| * Internal-facing APIs shared across files within the same layer (`lib/src/<layer>/`). Contributors should use true library-private (`_`) declarations first, and only use `@internal` package-private APIs when keeping the code in one file would put too many distinct concepts in a single file. | ||
|
|
||
| #### Relationship to `@visibleForTesting` and `@Deprecated` | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider adding this one:
I think this helps distinguish it from If you agree, do you think it would be possible to enforce this in an automated way? |
||
|
|
||
| * `@internal` is for package-private declarations omitted or hidden from public barrel exports. | ||
| * `@internal` APIs must be used internally. An `@internal` API that is referenced nowhere in the package's non-test code (outside `test/`, `integration_test/`, and other test directories) should be removed. References from tests do not count as uses; test-only access is what `@visibleForTesting` is for. | ||
| * `@internal` must not be used on APIs meant to be removed; use `@Deprecated` instead. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This rule relies on author intent, which can't be verified in review. As raised earlier in this thread, an exported API can be marked I'd suggest a mechanical rule instead: hiding an already-exported API from the barrel or marking it
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It should be treated as breaking change for sure. The loop hole is that once that is done we don't have guideline says when it can be actually removed. so a bad actor can come in mark it internal (with breaking change notice) and remove it immediately after. The deprecation policy does say we will wait at least a stable release before removing We could included that removing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's a good point. Thanks for clarifying. Maybe we should rule that adding
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sg |
||
| * `@visibleForTesting` remains discouraged on public declarations, because declarations exported by a public barrel file remain reachable by external consumers without importing `lib/src/`. | ||
|
|
||
| #### Placement rules | ||
|
|
||
| * `@internal` may only be applied to declarations inside `lib/src/`. | ||
| * For package-private members on a publicly exported class, prefer an `@internal` `extension` declared in the same file, excluded from `lib/<layer>.dart` with `hide`, and with both the `extension` and all its members annotated with `@internal` (an explicit exception to the style guide rule against `extension`s). | ||
|
|
||
| For example, `lib/src/widgets/route_state.dart` can keep the implementation private while allowing another file in the layer to invoke it through an internal extension: | ||
|
|
||
| ```dart | ||
| import 'package:meta/meta.dart'; | ||
|
|
||
| class RouteState { | ||
| void _finalizeRoute() { | ||
| // Implementation. | ||
| } | ||
| } | ||
|
|
||
| @internal | ||
| extension RouteStateInternal on RouteState { | ||
| @internal | ||
| void finalizeRoute() => _finalizeRoute(); | ||
| } | ||
|
Comment on lines
+79
to
+83
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would try to avoid this kind of thing unless it's really necessary. It's probably a sign that finalizeRoute should just be public. But I guess I imagine there could be a situation where we do need it.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yup, therefore I phrase it as discourage to use For this particular case, it is for separating navigator and route files, the alternative will be merging them navigator.dart and route.dart. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree with not allowing Repro (external package importing // package:lib_pkg/src/route_state.dart
import 'package:meta/meta.dart';
class RouteState {
void _finalizeRoute() {}
}
@internal
extension RouteStateInternal on RouteState {
void finalizeRoute() => _finalizeRoute();
}
extension RouteStateInternal2 on RouteState {
@internal
void finalizeRoute2() => _finalizeRoute();
}
@internal
void finalizeRoute3(RouteState s) => s._finalizeRoute();// package:app/main.dart
import 'package:lib_pkg/src/route_state.dart';
void main() {
final s = RouteState();
s.finalizeRoute(); // no diagnostic
RouteStateInternal(s).finalizeRoute(); // no diagnostic
s.finalizeRoute2(); // invalid_use_of_internal_member
finalizeRoute3(s); // invalid_use_of_internal_member
}Suggestion: recommend the top-level function as the workaround. Whether to allow There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This comment is not answered yet. There is no need of making an exception of using |
||
| ``` | ||
|
|
||
| The public barrel excludes the extension: | ||
|
|
||
| ```dart | ||
| export 'src/widgets/route_state.dart' hide RouteStateInternal; | ||
| ``` | ||
|
|
||
| An internal file in the same layer can still import the source file and use the extension: | ||
|
|
||
| ```dart | ||
| import 'route_state.dart'; | ||
|
|
||
| void finish(RouteState state) { | ||
| state.finalizeRoute(); | ||
| } | ||
| ``` | ||
|
|
||
| * When a package-private constructor or class member is needed for inheritance or construction on a publicly exported class, all such constructors and members must be annotated with `@internal`, and the class must be marked `final` or `sealed` so external classes cannot `extends` or `implements` it. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This rule contradicts itself on inheritance. A Assuming this rule is an answer to #30 (comment) : Constructors and |
||
| * `@internal` types must not appear in the public signatures or supertypes (`extends`, `implements`, `with`, `on`) of publicly exported declarations, because the analyzer does not flag `@internal` types leaked indirectly through exported APIs. Expose a public type or supertype instead, using an `@internal` constructor if construction must remain package-private. | ||
| * In any file in `lib/src/` not exported by a barrel file, all public top-level declarations must be annotated with `@internal`, subject to the cross-platform API policy below. | ||
|
|
||
| #### Cross-platform APIs | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. cc @loic-sharma and @justinmc , I added this section let me know what you think |
||
|
|
||
| **Open question:** Which approach should the guideline take for conditional-import implementations such as `_*_io.dart` and `_*_web.dart`? The preferred approach is option 2, which keeps platform-specific declarations internal and exposes the supported API through a public facade. | ||
|
|
||
| 1. **Exception:** Keep existing declarations as-is and treat conditional-import implementation files as an explicit exception to the rule requiring `@internal` on public top-level declarations in unexported files. | ||
| 2. **Public facade (preferred):** Annotate declarations in the implementation files with `@internal` and expose the supported API through a public facade that delegates to the selected implementation. For example: | ||
|
Comment on lines
+110
to
+111
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 2 looks good to me There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I also lean towards option 2. Defining public APIs inside |
||
|
|
||
| ```dart | ||
| // lib/src/widgets/device.dart | ||
| import '_device_stub.dart' | ||
| if (dart.library.io) '_device_io.dart' | ||
| if (dart.library.html) '_device_web.dart' as platform; | ||
|
|
||
| class Device { | ||
| Device() : _implementation = platform.DeviceImplementation(); | ||
|
|
||
| final platform.DeviceImplementation _implementation; | ||
|
|
||
| String get name => _implementation.name; | ||
|
|
||
| bool get isAvailable => _implementation.isAvailable; | ||
|
|
||
| Future<void> refresh() => _implementation.refresh(); | ||
| } | ||
| ``` | ||
|
|
||
| ```dart | ||
| // lib/src/widgets/_device_stub.dart | ||
| import 'package:meta/meta.dart'; | ||
|
|
||
| @internal | ||
| class DeviceImplementation { | ||
| String get name => 'Unknown device'; | ||
|
|
||
| bool get isAvailable => false; | ||
|
|
||
| Future<void> refresh() async {} | ||
| } | ||
| ``` | ||
|
|
||
| ```dart | ||
| // lib/src/widgets/_device_io.dart | ||
| import 'package:meta/meta.dart'; | ||
|
|
||
| @internal | ||
| class DeviceImplementation { | ||
| String get name => 'Native device'; | ||
|
|
||
| bool get isAvailable => true; | ||
|
|
||
| Future<void> refresh() async { | ||
| // Refresh native device state. | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ```dart | ||
| // lib/src/widgets/_device_web.dart | ||
| import 'package:meta/meta.dart'; | ||
|
|
||
| @internal | ||
| class DeviceImplementation { | ||
| String get name => 'Web device'; | ||
|
|
||
| bool get isAvailable => true; | ||
|
|
||
| Future<void> refresh() async { | ||
| // Refresh web device state. | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| The public barrel exports the facade, not the conditional implementations: | ||
|
|
||
| ```dart | ||
| export 'src/widgets/device.dart'; | ||
| ``` | ||
|
|
||
| Under the second approach, consumers use `Device`; the platform-specific implementations remain implementation details. | ||
|
|
||
| #### Barrel exports and file naming | ||
|
|
||
| * Drop the requirement that every file in `lib/src/<layer>/` be exported by `lib/<layer>.dart` (superseding [flutter/flutter#108632](https://github.com/flutter/flutter/issues/108632)). | ||
| * Any file in `lib/src/<layer>/` not exported by `lib/<layer>.dart` must have a `_` filename prefix (such as `lib/src/widgets/_accessibility_evaluations.dart`), and have all public top-level declarations annotated with `@internal`, subject to the cross-platform API policy above. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we automatically enforce this?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, I think so. |
||
| * When an `@internal` top-level declaration shares a file with public declarations, `lib/<layer>.dart` must exclude it with `hide` (such as `export 'src/widgets/foo.dart' hide InternalBar;`). Omitting `hide` in `lib/<layer>.dart` triggers [`invalid_export_of_internal_element`](https://dart.dev/tools/diagnostics/invalid_export_of_internal_element). (`hide` is not required on intra-`src/` re-exports.) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a valid use case for a file exposing both a public declaration and
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. a helper internal class that needs to access library private of a public class. class PublicClass {
final int _internal;
}
@internal
class Helper {
void print(PublicClasss a) {
print(a._internal);
}
}There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah I guess that might be annoying to split into a separate file. |
||
| * Cross-layer imports in `lib/src/` must continue importing top-level barrel files (`package:flutter/<layer>.dart`) rather than another layer's `lib/src/` files, with `lib/src/foundation/_features.dart` as the only permitted exception for framework-wide feature flags. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another thing to consider enforcing automatically if we can
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sg |
||
|
|
||
| ### TODOs | ||
|
|
||
| The analyzer already catches direct exports of `@internal` declarations from barrel files with [`invalid_export_of_internal_element`](https://dart.dev/tools/diagnostics/invalid_export_of_internal_element), and external usage with [`invalid_use_of_internal_member`](https://dart.dev/tools/diagnostics/invalid_use_of_internal_member) and `implementation_imports`. The remaining work: | ||
|
|
||
| 1. Update the [Flutter repository style guide](https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md) and [breaking change policy](https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes) to match this proposal, replacing the current ban on package-private APIs and the requirement that every file in `lib/src/<layer>/` be exported by `lib/<layer>.dart`. | ||
| 2. Add custom analyzer lint rules for single-library repository rules: | ||
| * In `_`-prefixed files in `lib/src/`, all public top-level declarations must be annotated with `@internal` (subject to the cross-platform API policy above). | ||
| * `@internal` types must not appear in the public signatures or supertypes (`extends`, `implements`, `with`, `on`) of publicly exported declarations. | ||
| * Public members of an `@internal` `extension` must also be annotated with `@internal`. | ||
| * Any publicly exported class with `@internal` constructors or members must be marked `final` or `sealed`. | ||
| * Files in `lib/src/<layer>/` must not import another layer's `lib/src/` files directly, except for `lib/src/foundation/_features.dart`. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I think this rule is still valuable, since keeping cross-layer "private imports" to a minimum is crucial for layer isolation, and for letting external code build on the lower layers (such as rendering) the same way the framework's own higher layers do. I suggest adding these two as exceptions for now. |
||
| 3. Add CI checks for cross-file and barrel rules: | ||
| * Any file in `lib/src/<layer>/` not exported by `lib/<layer>.dart` must have a `_` filename prefix, and `_`-prefixed files must not be exported by `lib/<layer>.dart`. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
As I mentioned in #30 (comment), it's sufficient to use While I agree that this convention can be a helpful visual cue, it has a practical cost:
Renaming a file is disruptive: every import of it has to be updated, links to it break, its history is lost from I'd suggest dropping the filename requirement, and keeping the |
||
| * Every `@internal` declaration must be referenced in the package's non-test code; references from tests do not count, and unused `@internal` declarations must be removed. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: I wonder if we need to make an exception to generated code, such as those in pigeon, which generates functions for all APIs regardless of whether they're used. cc @tarrinneal There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. pigeon shouldn't need an exception, but I don't know about the jnigen/ffigen output. |
||
| 4. Migrate existing cross-platform code (conditional-import `_*_io.dart` and `_*_web.dart` implementations) to the public facade approach. | ||
| 5. Audit existing files that contain too many distinct concepts, such as `lib/src/widgets/framework.dart` and `lib/src/widgets/basic.dart`, and split them into smaller files using `@internal` where cross-file access is needed. | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This phrasing is problematic.
@internalconstructors and members of exported classes, which line 102 allows. They're reachable through the barrel, so they fail "not exported", and changing them would still count as a breaking change, which defeats the point of marking them@internal.@internaldeclaration triggersinvalid_export_of_internal_element, and the planned checks require unexported declarations to be@internal.I recommend rephrasing the condition to be "annotated with
@internal" alone, which is likely equivalent to what you're describing but more accurate.