Skip to content

Dart: several type positions create no reference (extension on type, field types, generic arguments in expressions and top-level initializers) #2327

Closed

Description

@mg-mg-mg

Version: 1.6.2 (also current main, 6560052)

Summary

In Dart, callers / impact for a class only see parameter types, return types (including their generic arguments), and instantiations. A type named in these positions is never recorded as a reference:

Position Example Referenced?
parameter type int score(Report r) yes
return type generic argument Future<Report?> load() yes
extension target extension ReportX on Report {} no
class field type final Report report; no
generic argument in a top-level initializer final p = Family<Report?, String>(); no
generic argument in an expression Future<Report?>.value(null); no

In Flutter apps these are the common shapes for model extensions, generated API models (fields of other models), and Riverpod providers (FutureProvider.family<Report?, String>(…)), so model-to-UI impact is substantially incomplete. In one app, these positions account for most of the missed usages of shared API types. (Distinct from #1784, which is about extension type members.)

Reproduction

Run with CODEGRAPH=/path/to/codegraph bash repro.sh (creates and deletes a temporary project):

set -euo pipefail
CODEGRAPH="${CODEGRAPH:-codegraph}"
export DO_NOT_TRACK=1 CODEGRAPH_TELEMETRY=0 CODEGRAPH_NO_UPDATE_CHECK=1
proj="$(mktemp -d)"
trap 'rm -rf "$proj"' EXIT
index_project() { (cd "$proj" && git init -q && "$CODEGRAPH" init . >/dev/null 2>&1); }
callers_of() { (cd "$proj" && "$CODEGRAPH" callers "$1" 2>&1); }

mkdir -p "$proj/lib"
printf 'name: repro\nenvironment:\n  sdk: ">=3.0.0 <4.0.0"\n' >"$proj/pubspec.yaml"
cat >"$proj/lib/report.dart" <<'T'
class Report {
  final int score;
  const Report(this.score);
}
T
cat >"$proj/lib/a_parameter.dart" <<'T'
import 'report.dart';

int score(Report r) => r.score;
T
cat >"$proj/lib/b_return_generic.dart" <<'T'
import 'report.dart';

Future<Report?> load() async => null;
T
cat >"$proj/lib/c_extension_on.dart" <<'T'
import 'report.dart';

extension ReportX on Report {
  bool get high => score > 5;
}
T
cat >"$proj/lib/d_field_type.dart" <<'T'
import 'report.dart';

class Holder {
  final Report report;
  const Holder(this.report);
}
T
cat >"$proj/lib/e_toplevel_initializer_generic.dart" <<'T'
import 'report.dart';

class Family<T, A> {
  const Family();
}

final reportProvider = Family<Report?, String>();
T
cat >"$proj/lib/f_expression_generic.dart" <<'T'
import 'report.dart';

void run() {
  Future<Report?>.value(null);
}
T
index_project
echo "== callers Report (expected: files a through f)"; callers_of Report

Actual

Callers of "Report" (2):
function    load [references]
  lib/b_return_generic.dart:3
function    score [references]
  lib/a_parameter.dart:3

Expected

Files c through f are also listed (the extension ReportX, the class Holder or its field, reportProvider, and run).

Activity

  1. colbymchenry commented on Oct 5, 2026

    @colbymchenry
    Owner

    Thanks @mg-mg-mg for the position-by-position table and the repro script. The fix was written straight against them.

    Fixed on main in #2360. Dart type references used to come only from function and method signatures. They now also come from:

    • the type an extension is on (extension ReportX on Report)
    • field types (final Report report;), attributed to the class, since Dart fields have no symbols of their own, and enum fields
    • top-level variable types, and generic arguments in top-level final/const initializers (final reportProvider = Family<Report?, String>();)
    • everything a function body names as a type: local variable types, generic arguments in expressions (Future<Report?>.value(null)), casts, is checks and on X catch

    Constructor class names, import prefixes, built-in types and a class's references to itself are skipped. On your script, callers Report now lists ReportX, Holder, reportProvider and run alongside load and score. On bloc, riverpod and flutter/samples this added about 1,350, 4,870 and 670 type references and removed none.

    Still not covered: generic arguments in extends Bloc<E, S>, and calls inside top-level and static initializers, such as the bodies of Riverpod providers.

    This ships in the next release. Re-index after upgrading (codegraph index) to pick it up. You're credited in the changelog.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions