Skip to content

[checks_codegen] Generate full field types with imports - #2781

Merged
natebosch merged 5 commits into
masterfrom
checks-codegen-imports
Nov 6, 2026
Merged

natebosch merged 5 commits into
masterfrom
checks-codegen-imports

Conversation

@natebosch

@natebosch natebosch commented Oct 8, 2026 •

Copy link
Copy Markdown
Member

Generated getters referenced the field type by the name of its element
only, so type arguments were never imported and function typed fields
were skipped entirely. Imports were also searched only among the test
library's direct imports, so types reached by the checked class through
a re-export fell back to src/ libraries or uncompilable dart:_* SDK
libraries.

First walk the checked classes and their field types to collect every
referenced interface element and resolve an import URI for each, then
emit type references synchronously from the analyzer DartType:

  • Interface types import the library for each element, including within
    type arguments.
  • Function types, including generic function types with bounds, and
    record types are emitted structurally.
  • dynamic, void, and Never are emitted directly.
  • Type parameters of the checked class are replaced with their bounds by
    reading getter types from the class instantiated to bounds. The
    extension is on the raw type, so this matches the static type of
    v.field.
  • Unresolvable types (InvalidType) fail naming the class and field,
    with a source span on the field and a hint to check for a missing
    build dependency.

Function typed fields now get getters. Fields without a getter, such as
those introduced by a lone setter, are skipped.

Import resolution searches for a library exporting each non-public-SDK
element in order:

  1. The imports of the test library.
  2. The imports of every library declaring a referenced element.
  3. For elements declared in dart:_* libraries, public SDK libraries:
    first those reachable through imports and exports from the test and
    declaring libraries (so e.g. HttpStatus prefers dart:io over
    dart:html in a VM program), then any remaining public SDK
    libraries, each sorted by URI.

The first two groups prefer libraries outside src/. If no library
exports an element, fall back to its declaring library (which may be in
src/), or fail if that is a dart:_* library.

Note on conditional directives: build_runner resolves code with empty
DeclaredVariables, so conditional import/export directives always
follow their default URI during codegen, never an if (dart.library.*)
branch. When a barrel conditionally exports src/ implementations,
codegen now imports the barrel rather than the default src/ stub, so
test compilation evaluates the condition for the target platform. When a
declaring library uses a conditional import directly, codegen still
sees only the default branch: a stub default yields the stub's types as
before, while an IO default exposing types like HttpHeaders or
HttpStatus now emits dart:io (compiling on VM, instead of emitting
dart:_http/dart:_internal which failed everywhere) but still will
not compile for web unless the test library imports a compatible
exporter.

Generated getters referenced the field type by the name of its element
only, so type arguments were never imported and function typed fields
were skipped entirely.

Build the type reference recursively from the analyzer `DartType` with a
switch expression over the kinds of type, delegating interface, function,
and record types to separate helpers:

- Interface types import the library for each element, including within
  type arguments.
- Function types, including generic function types with bounds, and
  record types are emitted structurally.
- `dynamic`, `void`, and `Never` are emitted directly.
- Type parameters of the checked class are replaced with their bounds by
  reading getter types from the class instantiated to bounds. The
  extension is on the raw type, so this matches the static type of
  `v.field`.
- Unresolvable types (`InvalidType`) fail with a hint to check for a
  missing build dependency.

Function typed fields now get getters. Fields without a getter, such as
those introduced by a lone setter, are skipped.
@github-actions

github-actions Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

PR Health

Publish ✔️
Package Version Status Publish tag (post-merge)
package:checks_codegen ⭐ 0.1.1-wip WIP (no publish necessary)
  • 4 already published.
  • 4 WIP (no publish necessary).

Documentation at https://github.com/dart-lang/ecosystem/wiki/Publishing-automation.

This check can be disabled by tagging the PR with skip-publish-check.

Changelog Entry ✔️
Package Changed Files

Changes to files need to be accounted for in their respective changelogs.

This check can be disabled by tagging the PR with skip-changelog-check (for example, for non-user-visible changes when the package version is already at -wip).

@natebosch
natebosch marked this pull request as ready for review October 8, 2026 19:36
@natebosch
natebosch requested a review from davidmorgan October 8, 2026 19:36

@davidmorgan davidmorgan left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LGTM, thanks :)

];
return TypeReference(
(b) => b
..symbol = type.element.name

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Private types still don't compile, now as _i2._Private, including inside type arguments: List<_Private> get g; gives Subject<List<_i2._Private>> (analyzer: non_type_as_type_argument). #2782 skips private members but not public members with private types; this seems the natural place to handle it, or a follow-up.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Will handle skipping public members whose types reference private types in #2782 once this lands.

InterfaceType type,
Future<String?> Function(Element) importFor,
) async {
final import = await importFor(type.element);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

importFor searches only the test library's imports, so a type that foo.dart reached through a re-export falls back to its defining library: with import 'dart:io'; and HttpClient get c;, the output imports dart:_http, which doesn't compile; a barrel re-export of src/ gives a direct src/ import. Pre-existing for top-level field types, but this PR extends it to type arguments, e.g. List<HttpClient>.

Prior art in Mockito:

https://github.com/dart-lang/build/blob/0791f290931b211de17d49a695c413fc2d67947f/builder_pkgs/mockito/lib/src/builder.dart#L180-L298

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done — switched to a two-pass search modeled on Mockito: collect all referenced interface elements first, then search imports of the test library, imports of the declaring libraries (preferring libraries outside src/), and for private SDK elements the reachable public SDK libraries before falling back.

FunctionType() => await _functionTypeReference(type, importFor),
RecordType() => await _recordTypeReference(type, importFor),
_ => throw InvalidGenerationSourceError(
'Failed to resolve the type $type. '

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nit: for an unresolved type, $type prints InvalidType, so the message doesn't say which field or class failed; passing element: or the field name would help.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done — the error now names Class.field and passes element: field for a source span.

The error for an unresolved field type interpolated the type, which prints
as `InvalidType` and doesn't say where the problem is. Report the class and
field name, and attach the field element so the error includes a source span
for the declaration.
Previously a type was imported through one of the test library's imports if
one exported it, and otherwise from its declaring library. A type that the
declaring library of the checked class reached through a re-export, such as
HttpClient from dart:io or a class re-exported by a package's public library,
was imported from a src/ library, or from dart:_http which doesn't compile.

Follow the structure of mockito: first walk the checked classes and their
field types to collect every referenced class, then resolve an import for
each. A class is imported through the first library that exports it from:

1. The imports of the test library.
2. The imports of every library that declares a referenced class.
3. For classes declared in private SDK libraries, the public SDK libraries.
   Those reachable through imports and exports from the test library and the
   declaring libraries come first, so that HttpStatus comes from dart:io
   rather than dart:html in a VM program.

The first two groups prefer libraries outside of src/. If no library exports
a class it is still imported from its declaring library, which may be in
src/. It is an error if that is a private SDK library.

Resolving all imports up front makes code generation synchronous.
# Conflicts:
#	pkgs/checks_codegen/test/builder_test.dart
@natebosch
natebosch merged commit 66e062c into master Nov 6, 2026
70 checks passed
@natebosch
natebosch deleted the checks-codegen-imports branch November 6, 2026 00:05
copybara-service Bot pushed a commit to dart-lang/sdk that referenced this pull request Nov 12, 2026
Revisions updated by `dart tools/rev_sdk_deps.dart`.

core (https://github.com/dart-lang/core/compare/36f57a9..fa0c6b7):
  fa0c6b77  Fri Nov 6 12:56:58 2026 +0100  Michael Thomsen  Release os_detect 2.0.4 (dart-lang/core#1038)

dartdoc (https://github.com/dart-lang/dartdoc/compare/03ec6df..7e2508c):
  7e2508c6  Wed Nov 11 14:52:22 2026 +0200  Sergey G. Grekhov  Add constructor doc comment tests for enums and extension types (dart-lang/dartdoc#4316)
  03dfb467  Mon Nov 9 11:00:32 2026 +0200  Sergey G. Grekhov  `#3330`. Split test for parameters to separate tests (dart-lang/dartdoc#4314)
  23302d53  Mon Nov 9 10:56:34 2026 +0200  Sergey G. Grekhov  Add tests for primary constructors (dart-lang/dartdoc#4313)

ecosystem (https://github.com/dart-lang/ecosystem/compare/54e1673..1d2b5ff):
  1d2b5ff  Tue Nov 10 09:04:24 2026 -0800  Kevin Moore  feat(repo_manage): add --replace-pattern to report changelog (dart-lang/ecosystem#482)

http (https://github.com/dart-lang/http/compare/4646fe6..2bac218):
  2bac218  Fri Nov 6 17:49:23 2026 -0800  dart-ecosystem-bot[bot]  Tidy: package:cronet_http
  cdf967c  Fri Nov 6 15:40:49 2026 -0800  Brian Quinlan  chore(groundkeeper): use stable for flutter (dart-lang/http#2066)
  6582177  Thu Nov 5 13:04:35 2026 -0800  Brian Quinlan  doc: use timeout as an abort example (dart-lang/http#2062)

native (https://github.com/dart-lang/native/compare/6287745..b21ef2d):
  b21ef2da3  Wed Nov 11 16:07:34 2026 +0530  dhairya05  [json_syntax_generator] Pass the parameter name to ArgumentError.value (dart-lang/native#3787)
  20a607e48  Wed Nov 11 07:28:03 2026 -0300  Daco Harkes  [code_assets] [hooks] [hooks_runner] Adopt `.g.dart` for generated files (dart-lang/native#3790)
  cc974ddec  Wed Nov 11 19:59:39 2026 +1100  Liam Appelbe  [objective_c] Fix `isValidObject` bug on iOS simulator (dart-lang/native#3788)
  f7edbdee3  Sun Nov 8 13:18:25 2026 +1100  Liam Appelbe  [jni] Fix possible receiver NPEs (dart-lang/native#3784)
  0ca7d6745  Fri Nov 6 10:20:21 2026 -0300  Daco Harkes  [code_assets] [hooks_runner] [native_toolchain_c] Prepare for release (dart-lang/native#3781)
  baad15ab4  Fri Nov 6 02:56:52 2026 +0100  Kravchenko Igor  [objective_c] Link Foundation in the native support library (dart-lang/native#3782)

shelf (https://github.com/dart-lang/shelf/compare/9946de5..b175e9f):
  b175e9f  Tue Nov 10 09:14:31 2026 +0530  Muhammad Uvesh  shelf_static: ignore invalid If-Modified-Since headers (dart-lang/shelf#557)
  02b5f94  Sat Nov 7 23:09:28 2026 +0530  dhairya05  Fix typo in shelf_router docs (dart-lang/shelf#556)

test (https://github.com/dart-lang/test/compare/2a9e017..3533ae1):
  3533ae1e  Tue Nov 10 15:06:08 2026 -0800  Nate Bosch  Never retry tearDownAll (dart-lang/test#2815)
  5680b0b1  Fri Nov 6 12:52:11 2026 +0100  Sigurd Meldgaard  Add globalSetup and addGlobalTearDown APIs for cross-suite setup and teardown (dart-lang/test#2714)
  b8dc888a  Thu Nov 5 18:11:37 2026 -0800  Nate Bosch  [checks_codegen] Skip private fields and fields referencing private types (dart-lang/test#2782)
  66e062c4  Thu Nov 5 16:05:36 2026 -0800  Nate Bosch  [checks_codegen] Generate full field types with imports (dart-lang/test#2781)

tools (https://github.com/dart-lang/tools/compare/727fd9c..589d844):
  589d844c4  Wed Nov 11 20:27:32 2026 +0300  Hasan Armoush  fix(markdown): combine adjacent Text nodes when the element is last in its list (dart-lang/tools#2598)

Change-Id: I13acaa7378c86f2c33d88f3c4b163acf2b48ff08
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/576820
Commit-Queue: Nate Bosch <[email protected]>
Reviewed-by: Nicholas Shahan <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants