[roll] Update third-party dart packages
Roller-URL: https://ci.chromium.org/swarming/task/62fc4d545a034a11?server=chrome-swarming.appspot.com
CQ-Do-Not-Cancel-Tryjobs: true
Change-Id: I3c4bcb64215ec7e284cf60791440d942be4083c4
Reviewed-on: https://fuchsia-review.googlesource.com/c/third_party/dart-pkg/+/874941
Commit-Queue: GI Roller <global-integration-roller@fuchsia-infra.iam.gserviceaccount.com>
diff --git a/_discoveryapis_commons/BUILD.gn b/_discoveryapis_commons/BUILD.gn
index a157819..268ef6a 100644
--- a/_discoveryapis_commons/BUILD.gn
+++ b/_discoveryapis_commons/BUILD.gn
@@ -1,11 +1,11 @@
-# This file is generated by package_importer.py for _discoveryapis_commons-1.0.5
+# This file is generated by package_importer.py for _discoveryapis_commons-1.0.6
import("//build/dart/dart_library.gni")
dart_library("_discoveryapis_commons") {
package_name = "_discoveryapis_commons"
- language_version = "2.17"
+ language_version = "2.19"
disable_analysis = true
diff --git a/_discoveryapis_commons/CHANGELOG.md b/_discoveryapis_commons/CHANGELOG.md
index ebdf88c..310e5c4 100644
--- a/_discoveryapis_commons/CHANGELOG.md
+++ b/_discoveryapis_commons/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 1.0.6
+
+- Require Dart 2.19 or later.
+- Allow latest `package:http`.
+
## 1.0.5
- Drop use of `NullThrownError`.
diff --git a/_discoveryapis_commons/pubspec.yaml b/_discoveryapis_commons/pubspec.yaml
index 3a9916c..463b4f5 100644
--- a/_discoveryapis_commons/pubspec.yaml
+++ b/_discoveryapis_commons/pubspec.yaml
@@ -1,16 +1,16 @@
name: _discoveryapis_commons
-version: 1.0.5
+version: 1.0.6
description: Library for use by client APIs generated from Discovery Documents.
repository: https://github.com/google/googleapis.dart/tree/master/discoveryapis_commons
environment:
- sdk: '>=2.17.0 <3.0.0'
+ sdk: '>=2.19.0 <3.0.0'
dependencies:
- http: ^0.13.0
+ http: '>=0.13.5 <2.0.0'
http_parser: ^4.0.0
meta: ^1.3.0
dev_dependencies:
- lints: ^2.0.0
+ dart_flutter_team_lints: ^1.0.0
test: ^1.16.0
diff --git a/_fe_analyzer_shared/BUILD.gn b/_fe_analyzer_shared/BUILD.gn
index b63acb4..7c9f9b1 100644
--- a/_fe_analyzer_shared/BUILD.gn
+++ b/_fe_analyzer_shared/BUILD.gn
@@ -1,4 +1,4 @@
-# This file is generated by package_importer.py for _fe_analyzer_shared-54.0.0
+# This file is generated by package_importer.py for _fe_analyzer_shared-58.0.0
import("//build/dart/dart_library.gni")
@@ -18,16 +18,22 @@
"src/base/errors.dart",
"src/base/syntactic_entity.dart",
"src/deferred_function_literal_heuristic.dart",
- "src/exhaustiveness/equal.dart",
"src/exhaustiveness/exhaustive.dart",
- "src/exhaustiveness/intersect.dart",
- "src/exhaustiveness/intersect_empty.dart",
+ "src/exhaustiveness/key.dart",
+ "src/exhaustiveness/path.dart",
"src/exhaustiveness/profile.dart",
+ "src/exhaustiveness/shared.dart",
"src/exhaustiveness/space.dart",
"src/exhaustiveness/static_type.dart",
- "src/exhaustiveness/static_types.dart",
- "src/exhaustiveness/subtract.dart",
"src/exhaustiveness/test_helper.dart",
+ "src/exhaustiveness/types.dart",
+ "src/exhaustiveness/types/bool.dart",
+ "src/exhaustiveness/types/enum.dart",
+ "src/exhaustiveness/types/future_or.dart",
+ "src/exhaustiveness/types/list.dart",
+ "src/exhaustiveness/types/map.dart",
+ "src/exhaustiveness/types/record.dart",
+ "src/exhaustiveness/types/sealed.dart",
"src/exhaustiveness/witness.dart",
"src/experiments/errors.dart",
"src/experiments/flags.dart",
diff --git a/_fe_analyzer_shared/benchmark/exhaustiveness/large_fields_call_counts.dart b/_fe_analyzer_shared/benchmark/exhaustiveness/large_fields_call_counts.dart
index 3bbf1b4..e23a12e 100644
--- a/_fe_analyzer_shared/benchmark/exhaustiveness/large_fields_call_counts.dart
+++ b/_fe_analyzer_shared/benchmark/exhaustiveness/large_fields_call_counts.dart
@@ -3,9 +3,10 @@
// BSD-style license that can be found in the LICENSE file.
import 'package:_fe_analyzer_shared/src/exhaustiveness/exhaustive.dart';
+import 'package:_fe_analyzer_shared/src/exhaustiveness/path.dart';
import 'package:_fe_analyzer_shared/src/exhaustiveness/profile.dart' as profile;
-import 'package:_fe_analyzer_shared/src/exhaustiveness/space.dart';
import 'package:_fe_analyzer_shared/src/exhaustiveness/static_type.dart';
+import 'package:_fe_analyzer_shared/src/exhaustiveness/space.dart';
import '../../test/exhaustiveness/env.dart';
import '../../test/exhaustiveness/utils.dart';
@@ -21,9 +22,9 @@
var b = env.createClass('B', inherits: [a]);
var c = env.createClass('C', inherits: [a]);
var d = env.createClass('D', inherits: [a]);
- var t = env.createClass('T', fields: {'w': a, 'x': a, 'y': a, 'z': a});
+ var t = env.createRecordType({'w': a, 'x': a, 'y': a, 'z': a});
- expectExhaustiveOnlyAll(t, [
+ expectExhaustiveOnlyAll(env, t, [
{'w': b, 'x': b, 'y': b, 'z': b},
{'w': b, 'x': b, 'y': b, 'z': c},
{'w': b, 'x': b, 'y': b, 'z': d},
@@ -110,9 +111,11 @@
/// Test that [cases] are exhaustive over [type] if and only if all cases are
/// included and that all subsets of the cases are not exhaustive.
-void expectExhaustiveOnlyAll(StaticType type, List<Object> cases) {
- var spaces = parseSpaces(cases);
+void expectExhaustiveOnlyAll(ObjectFieldLookup objectFieldLookup,
+ StaticType type, List<Map<String, Object>> cases) {
+ var spaces = cases.map((c) => ty(type, c)).toList();
profile.reset();
- print(isExhaustive(Space(type), spaces));
+ print(
+ isExhaustive(objectFieldLookup, Space(const Path.root(), type), spaces));
profile.log();
}
diff --git a/_fe_analyzer_shared/benchmark/exhaustiveness/large_fields_timed.dart b/_fe_analyzer_shared/benchmark/exhaustiveness/large_fields_timed.dart
index a9613b4..dd3a6f9 100644
--- a/_fe_analyzer_shared/benchmark/exhaustiveness/large_fields_timed.dart
+++ b/_fe_analyzer_shared/benchmark/exhaustiveness/large_fields_timed.dart
@@ -5,6 +5,7 @@
import 'dart:math';
import 'package:_fe_analyzer_shared/src/exhaustiveness/exhaustive.dart';
+import 'package:_fe_analyzer_shared/src/exhaustiveness/path.dart';
import 'package:_fe_analyzer_shared/src/exhaustiveness/space.dart';
import 'package:_fe_analyzer_shared/src/exhaustiveness/static_type.dart';
@@ -20,9 +21,9 @@
var b = env.createClass('B', inherits: [a]);
var c = env.createClass('C', inherits: [a]);
var d = env.createClass('D', inherits: [a]);
- var t = env.createClass('T', fields: {'w': a, 'x': a, 'y': a, 'z': a});
+ var t = env.createRecordType({'w': a, 'x': a, 'y': a, 'z': a});
- expectExhaustiveOnlyAll(t, [
+ expectExhaustiveOnlyAll(env, t, [
{'w': b, 'x': b, 'y': b, 'z': b},
{'w': b, 'x': b, 'y': b, 'z': c},
{'w': b, 'x': b, 'y': b, 'z': d},
@@ -109,15 +110,18 @@
/// Test that [cases] are exhaustive over [type] if and only if all cases are
/// included and that all subsets of the cases are not exhaustive.
-void expectExhaustiveOnlyAll(StaticType type, List<Object> cases) {
+void expectExhaustiveOnlyAll(ObjectFieldLookup objectFieldLookup,
+ StaticType type, List<Map<String, Object>> cases) {
+ var valueSpace = Space(const Path.root(), type);
+ var caseSpaces = cases.map((c) => ty(type, c)).toList();
+
const trials = 100;
var best = 9999999;
for (var j = 0; j < 100000; j++) {
var watch = Stopwatch()..start();
for (var i = 0; i < trials; i++) {
- var spaces = parseSpaces(cases);
- var actual = isExhaustive(Space(type), spaces);
+ var actual = isExhaustive(objectFieldLookup, valueSpace, caseSpaces);
if (!actual) {
throw 'Expected exhaustive';
}
diff --git a/_fe_analyzer_shared/lib/src/exhaustiveness/equal.dart b/_fe_analyzer_shared/lib/src/exhaustiveness/equal.dart
deleted file mode 100644
index 63f527d..0000000
--- a/_fe_analyzer_shared/lib/src/exhaustiveness/equal.dart
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-import 'profile.dart' as profile;
-import 'space.dart';
-
-/// Returns `true` if [left] and [right] are equivalent spaces.
-///
-/// Equality is defined purely structurally/syntactically.
-bool equal(Space left, Space right, String reason) {
- profile.count('equal', reason);
-
- if (identical(left, right)) return true;
-
- // Empty is only equal to itself (and will get caught by the previous check).
- if (left == Space.empty) return false;
- if (right == Space.empty) return false;
-
- if (left is UnionSpace && right is UnionSpace) {
- return _equalUnions(left, right);
- }
-
- if (left is ExtractSpace && right is ExtractSpace) {
- return _equalExtracts(left, right);
- }
-
- // If we get here, one is a union and one is an extract.
- return false;
-}
-
-/// Returns `true` if [left] and [right] have the same type and the same fields
-/// with equal subspaces.
-bool _equalExtracts(ExtractSpace left, ExtractSpace right) {
- // Must have the same type.
- if (left.type != right.type) return false;
-
- // And the same fields.
- Set<String> fields = {...left.fields.keys, ...right.fields.keys};
- if (left.fields.length != fields.length) return false;
- if (right.fields.length != fields.length) return false;
-
- for (String field in fields) {
- if (!equal(left.fields[field]!, right.fields[field]!, 'recurse extract')) {
- return false;
- }
- }
-
- return true;
-}
-
-/// Returns `true` if [left] and [right] contain equal arms in any order.
-///
-/// Assumes that all duplicates have already been removed from each union.
-bool _equalUnions(UnionSpace left, UnionSpace right) {
- if (left.arms.length != right.arms.length) return false;
-
- /// For each left arm, should find an equal right arm.
- for (Space leftArm in left.arms) {
- bool found = false;
- for (Space rightArm in right.arms) {
- if (equal(leftArm, rightArm, 'recurse union')) {
- found = true;
- break;
- }
- }
- if (!found) return false;
- }
-
- return true;
-}
diff --git a/_fe_analyzer_shared/lib/src/exhaustiveness/exhaustive.dart b/_fe_analyzer_shared/lib/src/exhaustiveness/exhaustive.dart
index 2522a91..afb6bec 100644
--- a/_fe_analyzer_shared/lib/src/exhaustiveness/exhaustive.dart
+++ b/_fe_analyzer_shared/lib/src/exhaustiveness/exhaustive.dart
@@ -2,19 +2,18 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-import 'space.dart';
import 'static_type.dart';
-import 'subtract.dart';
+import 'key.dart';
+import 'path.dart';
+import 'profile.dart' as profile;
+import 'space.dart';
import 'witness.dart';
-/// Returns `true` if [cases] exhaustively covers all possible values of
-/// [value].
-///
-/// This is defined simply in terms of subtraction and unions: [cases] is a
-/// union space, and it's exhaustive if subtracting it from [value] leaves
-/// nothing.
-bool isExhaustive(Space value, List<Space> cases) {
- return subtract(value, new Space.union(cases)) == Space.empty;
+/// Returns `true` if [caseSpaces] exhaustively covers all possible values of
+/// [valueSpace].
+bool isExhaustive(
+ ObjectFieldLookup fieldLookup, Space valueSpace, List<Space> caseSpaces) {
+ return checkExhaustiveness(fieldLookup, valueSpace, caseSpaces) == null;
}
/// Checks the [cases] representing a series of switch cases to see if they
@@ -22,119 +21,274 @@
/// checks to see if any case can't be matched because it's covered by previous
/// cases.
///
-/// Returns a string containing any unreachable case or non-exhaustive match
-/// errors. Returns an empty string if all cases are reachable and the cases
-/// are exhaustive.
+/// Returns a list of any unreachable case or non-exhaustive match errors.
+/// Returns an empty list if all cases are reachable and the cases are
+/// exhaustive.
+List<ExhaustivenessError> reportErrors(
+ ObjectFieldLookup fieldLookup, StaticType valueType, List<Space> cases) {
+ _Checker checker = new _Checker(fieldLookup);
-List<ExhaustivenessError> reportErrors(StaticType valueType, List<Space> cases,
- [List<Space>? remainingSpaces]) {
- return reportErrorsNew(valueType, cases);
-}
-
-List<ExhaustivenessError> reportErrorsOld(
- StaticType valueType, List<Space> cases,
- [List<Space>? remainingSpaces]) {
List<ExhaustivenessError> errors = <ExhaustivenessError>[];
- Space remaining = new Space(valueType);
- for (int i = 0; i < cases.length; i++) {
+ Space valuePattern = new Space(const Path.root(), valueType);
+ List<List<Space>> caseRows = cases.map((space) => [space]).toList();
+
+ for (int i = 1; i < caseRows.length; i++) {
// See if this case is covered by previous ones.
- if (i > 0) {
- Space previous = new Space.union(cases.sublist(0, i));
- if (subtract(cases[i], previous) == Space.empty) {
- errors.add(new UnreachableCaseErrorOld(valueType, cases, i, previous));
- }
+ if (checker._unmatched(caseRows.sublist(0, i), caseRows[i]) == null) {
+ errors.add(new UnreachableCaseError(valueType, cases, i));
}
-
- remainingSpaces?.add(remaining);
- remaining = subtract(remaining, cases[i]);
}
- remainingSpaces?.add(remaining);
- if (remaining != Space.empty) {
- errors.add(new NonExhaustiveErrorOld(valueType, cases, remaining));
+ Witness? witness = checker._unmatched(caseRows, [valuePattern]);
+ if (witness != null) {
+ errors.add(new NonExhaustiveError(valueType, cases, witness));
}
return errors;
}
+/// Determines if [cases] is exhaustive over all values contained by
+/// [valueSpace]. If so, returns `null`. Otherwise, returns a string describing
+/// an example of one value that isn't matched by anything in [cases].
+Witness? checkExhaustiveness(
+ ObjectFieldLookup fieldLookup, Space valueSpace, List<Space> cases) {
+ _Checker checker = new _Checker(fieldLookup);
+
+ // TODO(johnniwinther): Perform reachability checking.
+ List<List<Space>> caseRows = cases.map((space) => [space]).toList();
+
+ Witness? witness = checker._unmatched(caseRows, [valueSpace]);
+
+ // Uncomment this to have it print out the witness for non-exhaustive matches.
+ // if (witness != null) print(witness);
+
+ return witness;
+}
+
+class _Checker {
+ final ObjectFieldLookup _fieldLookup;
+
+ _Checker(this._fieldLookup);
+
+ /// Tries to find a pattern containing at least one value matched by
+ /// [valuePatterns] that is not matched by any of the patterns in [caseRows].
+ ///
+ /// If found, returns it. This is a witness example showing that [caseRows] is
+ /// not exhaustive over all values in [valuePatterns]. If it returns `null`,
+ /// then [caseRows] exhaustively covers [valuePatterns].
+ Witness? _unmatched(List<List<Space>> caseRows, List<Space> valuePatterns,
+ [List<Predicate> witnessPredicates = const []]) {
+ assert(caseRows.every((element) => element.length == valuePatterns.length),
+ "Value patterns: $valuePatterns, case rows: $caseRows.");
+ profile.count('_unmatched');
+ // If there are no more columns, then we've tested all the predicates we
+ // have to test.
+ if (valuePatterns.isEmpty) {
+ // If there are still any rows left, then it means every remaining value
+ // will go to one of those rows' bodies, so we have successfully matched.
+ if (caseRows.isNotEmpty) return null;
+
+ // If we ran out of rows too, then it means [witnessPredicates] is now a
+ // complete description of at least one value that slipped past all the
+ // rows.
+ return new Witness(witnessPredicates);
+ }
+
+ // Look down the first column of tests.
+ Space firstValuePatterns = valuePatterns[0];
+
+ Set<Key> keysOfInterest = {};
+ for (List<Space> caseRow in caseRows) {
+ for (SingleSpace singleSpace in caseRow.first.singleSpaces) {
+ keysOfInterest.addAll(singleSpace.additionalFields.keys);
+ }
+ }
+ for (SingleSpace firstValuePattern in firstValuePatterns.singleSpaces) {
+ // TODO(johnniwinther): Right now, this brute force expands all subtypes
+ // of sealed types and considers them individually. It would be faster to
+ // look at the types of the patterns in the first column of each row and
+ // only expand subtypes that are actually tested.
+ // Split the type into its sealed subtypes and consider each one
+ // separately. This enables it to filter rows more effectively.
+ List<StaticType> subtypes =
+ expandSealedSubtypes(firstValuePattern.type, keysOfInterest);
+ for (StaticType subtype in subtypes) {
+ Witness? result = _filterByType(subtype, caseRows, firstValuePattern,
+ valuePatterns, witnessPredicates, firstValuePatterns.path);
+
+ // If we found a witness for a subtype that no rows match, then we can
+ // stop. There may be others but we don't need to find more.
+ if (result != null) return result;
+ }
+ }
+
+ // If we get here, no subtype yielded a witness, so we must have matched
+ // everything.
+ return null;
+ }
+
+ Witness? _filterByType(
+ StaticType type,
+ List<List<Space>> caseRows,
+ SingleSpace firstSingleSpaceValue,
+ List<Space> valueSpaces,
+ List<Predicate> witnessPredicates,
+ Path path) {
+ profile.count('_filterByType');
+ // Extend the witness with the type we're matching.
+ List<Predicate> extendedWitness = [
+ ...witnessPredicates,
+ new Predicate(path, type)
+ ];
+
+ // 1) Discard any rows that might not match because the column's type isn't
+ // a subtype of the value's type. We only keep rows that *must* match
+ // because a row that could potentially fail to match will not help us prove
+ // exhaustiveness.
+ //
+ // 2) Expand any unions in the first column. This can (deliberately) produce
+ // duplicate rows in remainingRows.
+ List<SingleSpace> remainingRowFirstSingleSpaces = [];
+ List<List<Space>> remainingRows = [];
+ for (List<Space> row in caseRows) {
+ Space firstSpace = row[0];
+
+ for (SingleSpace firstSingleSpace in firstSpace.singleSpaces) {
+ // If the row's type is a supertype of the value pattern's type then it
+ // must match.
+ if (type.isSubtypeOf(firstSingleSpace.type)) {
+ remainingRowFirstSingleSpaces.add(firstSingleSpace);
+ remainingRows.add(row);
+ }
+ }
+ }
+
+ // We have now filtered by the type test of the first column of patterns,
+ // but some of those may also have field subpatterns. If so, lift those out
+ // so we can recurse into them.
+ Set<Key> fieldKeys = {
+ ...firstSingleSpaceValue.fields.keys,
+ for (SingleSpace firstPattern in remainingRowFirstSingleSpaces)
+ ...firstPattern.fields.keys
+ };
+
+ Set<Key> additionalFieldKeys = {
+ ...firstSingleSpaceValue.additionalFields.keys,
+ for (SingleSpace firstPattern in remainingRowFirstSingleSpaces)
+ ...firstPattern.additionalFields.keys
+ };
+
+ // Sorting isn't necessary, but makes the behavior deterministic.
+ List<Key> sortedFieldKeys = fieldKeys.toList()..sort();
+ List<Key> sortedAdditionalFieldKeys = additionalFieldKeys.toList()..sort();
+
+ // Remove the first column from the value list and replace it with any
+ // expanded fields.
+ valueSpaces = [
+ ..._expandFields(sortedFieldKeys, sortedAdditionalFieldKeys,
+ firstSingleSpaceValue, type, path),
+ ...valueSpaces.skip(1)
+ ];
+
+ // Remove the first column from each row and replace it with any expanded
+ // fields.
+ for (int i = 0; i < remainingRows.length; i++) {
+ remainingRows[i] = [
+ ..._expandFields(
+ sortedFieldKeys,
+ sortedAdditionalFieldKeys,
+ remainingRowFirstSingleSpaces[i],
+ remainingRowFirstSingleSpaces[i].type,
+ path),
+ ...remainingRows[i].skip(1)
+ ];
+ }
+
+ // Proceed to the next column.
+ return _unmatched(remainingRows, valueSpaces, extendedWitness);
+ }
+
+ /// Given a list of [fieldKeys] and [additionalFieldKeys], and a
+ /// [singleSpace], generates a list of single spaces, one for each named field
+ /// and additional field key.
+ ///
+ /// When [singleSpace] contains a field with that name or an additional field
+ /// with the key, extracts it into the resulting list. Otherwise, the
+ /// [singleSpace] doesn't care about that field, so inserts a default [Space]
+ /// that matches all values for the field.
+ ///
+ /// In other words, this unpacks a set of fields so that the main algorithm
+ /// can add them to the worklist.
+ List<Space> _expandFields(List<Key> fieldKeys, List<Key> additionalFieldKeys,
+ SingleSpace singleSpace, StaticType type, Path path) {
+ profile.count('_expandFields');
+ List<Space> result = <Space>[];
+ for (Key key in fieldKeys) {
+ Space? field = singleSpace.fields[key];
+ if (field != null) {
+ result.add(field);
+ } else {
+ // This pattern doesn't test this field, so add a pattern for the
+ // field that matches all values. This way the columns stay aligned.
+ result.add(new Space(path.add(key),
+ type.getField(_fieldLookup, key) ?? StaticType.nullableObject));
+ }
+ }
+ for (Key key in additionalFieldKeys) {
+ Space? field = singleSpace.additionalFields[key];
+ if (field != null) {
+ result.add(field);
+ } else {
+ // This pattern doesn't test this field, so add a pattern for the
+ // field that matches all values. This way the columns stay aligned.
+ result.add(new Space(path.add(key),
+ type.getAdditionalField(key) ?? StaticType.nullableObject));
+ }
+ }
+ return result;
+ }
+}
+
+/// Recursively expands [type] with its subtypes if its sealed.
+///
+/// Otherwise, just returns [type].
+List<StaticType> expandSealedSubtypes(
+ StaticType type, Set<Key> keysOfInterest) {
+ profile.count('expandSealedSubtypes');
+ if (!type.isSealed) {
+ return [type];
+ } else {
+ return {
+ for (StaticType subtype in type.getSubtypes(keysOfInterest))
+ ...expandSealedSubtypes(subtype, keysOfInterest)
+ }.toList();
+ }
+}
+
class ExhaustivenessError {}
-abstract class NonExhaustiveError implements ExhaustivenessError {
- StaticType get valueType;
- List<Space> get cases;
- String get witness;
-}
-
-class NonExhaustiveErrorNew implements NonExhaustiveError {
- @override
+class NonExhaustiveError implements ExhaustivenessError {
final StaticType valueType;
- @override
final List<Space> cases;
- @override
- final String witness;
+ final Witness witness;
- NonExhaustiveErrorNew(this.valueType, this.cases, this.witness);
+ NonExhaustiveError(this.valueType, this.cases, this.witness);
@override
String toString() =>
- '$valueType is not exhaustively matched by ${new Space.union(cases)}.';
+ '$valueType is not exhaustively matched by ${cases.join('|')}.';
}
-class NonExhaustiveErrorOld implements NonExhaustiveError {
- @override
+class UnreachableCaseError implements ExhaustivenessError {
final StaticType valueType;
-
- @override
final List<Space> cases;
-
- final Space remaining;
-
- NonExhaustiveErrorOld(this.valueType, this.cases, this.remaining);
-
- @override
- String get witness => '$remaining';
-
- @override
- String toString() =>
- '$valueType is not exhaustively matched by ${new Space.union(cases)}.';
-}
-
-abstract class UnreachableCaseError implements ExhaustivenessError {
- StaticType get valueType;
- List<Space> get cases;
- int get index;
-}
-
-class UnreachableCaseErrorNew implements UnreachableCaseError {
- @override
- final StaticType valueType;
- @override
- final List<Space> cases;
- @override
final int index;
- UnreachableCaseErrorNew(this.valueType, this.cases, this.index);
+ UnreachableCaseError(this.valueType, this.cases, this.index);
@override
String toString() => 'Case #${index + 1} ${cases[index]} is unreachable.';
}
-
-class UnreachableCaseErrorOld implements UnreachableCaseError {
- @override
- final StaticType valueType;
- @override
- final List<Space> cases;
- @override
- final int index;
- final Space previous;
-
- UnreachableCaseErrorOld(
- this.valueType, this.cases, this.index, this.previous);
-
- @override
- String toString() =>
- 'Case #${index + 1} ${cases[index]} is covered by $previous.';
-}
diff --git a/_fe_analyzer_shared/lib/src/exhaustiveness/intersect.dart b/_fe_analyzer_shared/lib/src/exhaustiveness/intersect.dart
deleted file mode 100644
index 57a6dfa..0000000
--- a/_fe_analyzer_shared/lib/src/exhaustiveness/intersect.dart
+++ /dev/null
@@ -1,107 +0,0 @@
-// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-import 'profile.dart' as profile;
-import 'space.dart';
-import 'static_type.dart';
-
-/// Calculates the intersection of [left] and [right].
-///
-/// This is used to tell if two field spaces on a pair of spaces being
-/// subtracted have no common values.
-Space intersect(Space left, Space right) {
- profile.count('intersect');
-
- // The intersection with an empty space is always empty.
- if (left == Space.empty) return Space.empty;
- if (right == Space.empty) return Space.empty;
-
- // The intersection of a union is the union of the intersections of its arms.
- if (left is UnionSpace) {
- return new Space.union(
- left.arms.map((arm) => intersect(arm, right)).toList());
- }
-
- if (right is UnionSpace) {
- return new Space.union(
- right.arms.map((arm) => intersect(left, arm)).toList());
- }
-
- // Otherwise, we're intersecting two [ExtractSpaces].
- return _intersectExtracts(left as ExtractSpace, right as ExtractSpace);
-}
-
-/// Returns the intersection of two static types [left] and [right].
-///
-/// Returns `StaticType.neverType` if the intersection is empty.
-StaticType intersectTypes(StaticType left, StaticType right) {
- // If one type is a subtype, the subtype is the intersection.
- if (left.isSubtypeOf(right)) return left;
- if (right.isSubtypeOf(left)) return right;
-
- if (left is NullableStaticType) {
- if (right is NullableStaticType) {
- StaticType intersection =
- intersectTypes(left.underlying, right.underlying);
- if (intersection == StaticType.neverType) return StaticType.neverType;
- return intersection.nullable;
- } else {
- return intersectTypes(left.underlying, right);
- }
- } else if (right is NullableStaticType) {
- return intersectTypes(left, right.underlying);
- }
-
- // If we allow sealed types to share subtypes, then this will need to be more
- // sophisticated. Here:
- //
- // (A) (B)
- // / \ / \
- // C D E
- //
- // The intersection of A and B should be D. Here:
- //
- // (A) (B)
- // | \ / |
- // |\ \/ /|
- // | \/\/ |
- // C D E F
- //
- // It should be D, E.
-
- // Unrelated types.
- return StaticType.neverType;
-}
-
-/// Returns the interaction of extract spaces [left] and [right].
-Space _intersectExtracts(ExtractSpace left, ExtractSpace right) {
- StaticType type = intersectTypes(left.type, right.type);
-
- // If the types are disjoint, the intersection is empty.
- if (type == StaticType.neverType) return Space.empty;
-
- // Recursively intersect the fields.
- List<String> fieldNames =
- {...left.fields.keys, ...right.fields.keys}.toList();
-
- // Sorting isn't needed for correctness, just to make the tests less brittle.
- fieldNames.sort();
-
- Map<String, Space> fields = <String, Space>{};
- for (String name in fieldNames) {
- Space field = _intersectFields(left.fields[name], right.fields[name]);
-
- // If the fields are disjoint, then the entire space will have no values.
- if (field == Space.empty) return Space.empty;
- fields[name] = field;
- }
-
- return new Space(type, fields);
-}
-
-Space _intersectFields(Space? left, Space? right) {
- if (left == null) return right!;
- if (right == null) return left;
- return intersect(left, right);
-}
diff --git a/_fe_analyzer_shared/lib/src/exhaustiveness/intersect_empty.dart b/_fe_analyzer_shared/lib/src/exhaustiveness/intersect_empty.dart
deleted file mode 100644
index b756365..0000000
--- a/_fe_analyzer_shared/lib/src/exhaustiveness/intersect_empty.dart
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-import 'space.dart';
-import 'static_type.dart';
-
-/// Calculates whether the intersection of [left] and [right] is empty.
-///
-/// This is used to tell if two field spaces on a pair of spaces being
-/// subtracted have no common values.
-bool spacesHaveEmptyIntersection(Space left, Space right) {
- // The intersection with an empty space is always empty.
- if (left == Space.empty) return true;
- if (right == Space.empty) return true;
-
- // The intersection of a union is empty if all of the arms are.
- if (left is UnionSpace) {
- return left.arms.every((arm) => spacesHaveEmptyIntersection(arm, right));
- }
-
- if (right is UnionSpace) {
- return right.arms.every((arm) => spacesHaveEmptyIntersection(left, arm));
- }
-
- // Otherwise, we're intersecting two [ExtractSpaces].
- return _extractsHaveEmptyIntersection(
- left as ExtractSpace, right as ExtractSpace);
-}
-
-/// Returns true if the intersection of two static types [left] and [right] is
-/// empty.
-bool typesHaveEmptyIntersection(StaticType left, StaticType right) {
- // If one type is a subtype, the subtype is the intersection.
- if (left.isSubtypeOf(right)) return false;
- if (right.isSubtypeOf(left)) return false;
-
- // Unrelated types.
- return true;
-}
-
-/// Returns the interaction of extract spaces [left] and [right].
-bool _extractsHaveEmptyIntersection(ExtractSpace left, ExtractSpace right) {
- if (typesHaveEmptyIntersection(left.type, right.type)) return true;
-
- // Recursively intersect the fields.
- List<String> fieldNames =
- {...left.fields.keys, ...right.fields.keys}.toList();
- for (String name in fieldNames) {
- // If the fields are disjoint, then the entire space will have no values.
- if (_fieldsHaveEmptyIntersection(left.fields[name], right.fields[name])) {
- return true;
- }
- }
-
- return false;
-}
-
-bool _fieldsHaveEmptyIntersection(Space? left, Space? right) {
- if (left == null) return right! == Space.empty;
- if (right == null) return left == Space.empty;
- return spacesHaveEmptyIntersection(left, right);
-}
diff --git a/_fe_analyzer_shared/lib/src/exhaustiveness/key.dart b/_fe_analyzer_shared/lib/src/exhaustiveness/key.dart
new file mode 100644
index 0000000..1f7fefc
--- /dev/null
+++ b/_fe_analyzer_shared/lib/src/exhaustiveness/key.dart
@@ -0,0 +1,223 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// A value that defines the key of an additional field.
+///
+/// This is used for accessing entries in maps and elements in lists.
+abstract class Key implements Comparable<Key> {
+ String get name;
+}
+
+/// An entry in a map whose key is a constant [value].
+class MapKey extends Key {
+ final Object value;
+ final String valueAsText;
+
+ MapKey(this.value, this.valueAsText);
+
+ @override
+ String get name => '[$valueAsText]';
+
+ @override
+ int get hashCode => value.hashCode;
+
+ @override
+ bool operator ==(Object other) {
+ if (identical(this, other)) return true;
+ return other is MapKey && value == other.value;
+ }
+
+ @override
+ String toString() => valueAsText;
+
+ @override
+ int compareTo(Key other) {
+ if (other is MapKey) {
+ return valueAsText.compareTo(other.valueAsText);
+ } else if (other is HeadKey || other is RestKey || other is TailKey) {
+ // Map keys after list keys.
+ return 1;
+ } else {
+ // Map keys before record index and name keys,
+ return -1;
+ }
+ }
+}
+
+/// Tagging interface for the list specific keys [HeadKey], [RestKey], and
+/// [TailKey].
+abstract class ListKey implements Key {}
+
+/// An element in a list accessed by an [index] from the start of the list.
+class HeadKey extends Key implements ListKey {
+ final int index;
+
+ HeadKey(this.index);
+
+ @override
+ String get name => '[$index]';
+
+ @override
+ int get hashCode => index.hashCode;
+
+ @override
+ bool operator ==(Object other) {
+ if (identical(this, other)) return true;
+ return other is HeadKey && index == other.index;
+ }
+
+ @override
+ String toString() => 'HeadKey($index)';
+
+ @override
+ int compareTo(Key other) {
+ if (other is HeadKey) {
+ return index.compareTo(other.index);
+ } else {
+ // Head keys before other keys,
+ return -1;
+ }
+ }
+}
+
+/// An element in a list accessed by an [index] from the end of the list, that
+/// is, the [index]th last element.
+class TailKey extends Key implements ListKey {
+ final int index;
+
+ TailKey(this.index);
+
+ @override
+ String get name => '[${-(index + 1)}]';
+
+ @override
+ int get hashCode => index.hashCode;
+
+ @override
+ bool operator ==(Object other) {
+ if (identical(this, other)) return true;
+ return other is TailKey && index == other.index;
+ }
+
+ @override
+ String toString() => 'TailKey($index)';
+
+ @override
+ int compareTo(Key other) {
+ if (other is TailKey) {
+ return -index.compareTo(other.index);
+ } else if (other is HeadKey || other is RestKey) {
+ // Tail keys after head and rest keys.
+ return 1;
+ } else {
+ // Tail keys before map, record index and name keys,
+ return -1;
+ }
+ }
+}
+
+/// A sublist of a list from the [headSize]th index to the [tailSize]th last
+/// index.
+class RestKey extends Key implements ListKey {
+ final int headSize;
+ final int tailSize;
+
+ RestKey(this.headSize, this.tailSize);
+
+ @override
+ String get name => '[$headSize:${-tailSize}]';
+
+ @override
+ int get hashCode => Object.hash(headSize, tailSize);
+
+ @override
+ bool operator ==(Object other) {
+ if (identical(this, other)) return true;
+ return other is RestKey &&
+ headSize == other.headSize &&
+ tailSize == other.tailSize;
+ }
+
+ @override
+ String toString() => 'RestKey($headSize,$tailSize)';
+
+ @override
+ int compareTo(Key other) {
+ if (other is RestKey) {
+ int result = headSize.compareTo(other.headSize);
+ if (result == 0) {
+ result = -tailSize.compareTo(other.tailSize);
+ }
+ return result;
+ } else if (other is HeadKey) {
+ // Rest keys after head keys.
+ return 1;
+ } else {
+ // Rest keys before tail, map, record index and name keys,
+ return -1;
+ }
+ }
+}
+
+/// Key for a regular object member.
+class NameKey extends Key {
+ @override
+ final String name;
+
+ NameKey(this.name);
+
+ @override
+ int get hashCode => name.hashCode;
+
+ @override
+ bool operator ==(Object other) {
+ if (identical(this, other)) return true;
+ return other is NameKey && name == other.name;
+ }
+
+ @override
+ String toString() => 'NameKey($name)';
+
+ @override
+ int compareTo(Key other) {
+ if (other is RecordIndexKey) {
+ // Name keys after record index keys.
+ return 1;
+ } else if (other is NameKey) {
+ return name.compareTo(other.name);
+ } else {
+ // Name keys after other keys.
+ return 1;
+ }
+ }
+}
+
+/// Tagging interface for the record specific keys [RecordIndexKey] and
+/// [RecordNameKey].
+abstract class RecordKey implements Key {}
+
+/// Specialized [NameKey] for an indexed record field.
+class RecordIndexKey extends NameKey implements RecordKey {
+ final int index;
+
+ RecordIndexKey(this.index) : super('\$${index + 1}');
+
+ @override
+ int compareTo(Key other) {
+ if (other is RecordIndexKey) {
+ return index.compareTo(other.index);
+ } else if (other is NameKey) {
+ // Record index keys before name keys.
+ return -1;
+ } else {
+ // Record index keys after other keys.
+ return 1;
+ }
+ }
+}
+
+/// Specialized [NameKey] for a named record field.
+class RecordNameKey extends NameKey implements RecordKey {
+ RecordNameKey(super.name);
+}
diff --git a/_fe_analyzer_shared/lib/src/exhaustiveness/path.dart b/_fe_analyzer_shared/lib/src/exhaustiveness/path.dart
new file mode 100644
index 0000000..364b177
--- /dev/null
+++ b/_fe_analyzer_shared/lib/src/exhaustiveness/path.dart
@@ -0,0 +1,84 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'key.dart';
+
+/// A path that describes location of a [SingleSpace] from the root of
+/// enclosing [Space].
+abstract class Path {
+ const Path();
+
+ /// Create root path.
+ const factory Path.root() = _Root;
+
+ /// Returns a path that adds a step by the [key] to the current path.
+ Path add(Key key) => new _Step(this, key);
+
+ void _toList(List<Key> list);
+
+ /// Returns a list of the keys from the root to this path.
+ List<Key> toList();
+}
+
+/// The root path object.
+class _Root extends Path {
+ const _Root();
+
+ @override
+ void _toList(List<Key> list) {}
+
+ @override
+ List<Key> toList() => const [];
+
+ @override
+ int get hashCode => 1729;
+
+ @override
+ bool operator ==(Object other) {
+ return other is _Root;
+ }
+
+ @override
+ String toString() => '@';
+}
+
+/// A single step in a path that holds the [parent] pointer and the [key] for
+/// the step.
+class _Step extends Path {
+ final Path parent;
+ final Key key;
+
+ _Step(this.parent, this.key);
+
+ @override
+ List<Key> toList() {
+ List<Key> list = [];
+ _toList(list);
+ return list;
+ }
+
+ @override
+ void _toList(List<Key> list) {
+ parent._toList(list);
+ list.add(key);
+ }
+
+ @override
+ late final int hashCode = Object.hash(parent, key);
+
+ @override
+ bool operator ==(Object other) {
+ if (identical(this, other)) return true;
+ return other is _Step && key == other.key && parent == other.parent;
+ }
+
+ @override
+ String toString() {
+ if (parent is _Root) {
+ return key.name;
+ } else {
+ return '$parent.${key.name}';
+ }
+ }
+}
diff --git a/_fe_analyzer_shared/lib/src/exhaustiveness/shared.dart b/_fe_analyzer_shared/lib/src/exhaustiveness/shared.dart
new file mode 100644
index 0000000..d3a7048
--- /dev/null
+++ b/_fe_analyzer_shared/lib/src/exhaustiveness/shared.dart
@@ -0,0 +1,715 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'exhaustive.dart';
+import 'key.dart';
+import 'path.dart';
+import 'space.dart';
+import 'static_type.dart';
+import 'types.dart';
+
+/// Interface implemented by analyze/CFE to support type operations need for the
+/// shared [StaticType]s.
+abstract class TypeOperations<Type extends Object> {
+ /// Returns the type for `Object?`.
+ Type get nullableObjectType;
+
+ /// Returns the type for the non-nullable `Object`.
+ Type get nonNullableObjectType;
+
+ /// Returns `true` if [s] is a subtype of [t].
+ bool isSubtypeOf(Type s, Type t);
+
+ /// Returns a type that overapproximates the possible values of [type] by
+ /// replacing all type variables with the default types.
+ Type overapproximate(Type type);
+
+ /// Returns `true` if [type] is a potentially nullable type.
+ bool isNullable(Type type);
+
+ /// Returns the non-nullable type corresponding to [type]. For instance
+ /// `Foo` for `Foo?`. If [type] is already non-nullable, it itself is
+ /// returned.
+ Type getNonNullable(Type type);
+
+ /// Returns `true` if [type] is the `Null` type.
+ bool isNullType(Type type);
+
+ /// Returns `true` if [type] is the `Never` type.
+ bool isNeverType(Type type);
+
+ /// Returns `true` if [type] is the `Object?` type.
+ bool isNullableObject(Type type);
+
+ /// Returns `true` if [type] is the `Object` type.
+ bool isNonNullableObject(Type type);
+
+ /// Returns `true` if [type] is the `dynamic` type.
+ bool isDynamic(Type type);
+
+ /// Returns `true` if [type] is the `bool` type.
+ bool isBoolType(Type type);
+
+ /// Returns the `bool` type.
+ Type get boolType;
+
+ /// Returns `true` if [type] is a record type.
+ bool isRecordType(Type type);
+
+ /// Returns `true` if [type] is a generic interface type.
+ bool isGeneric(Type type);
+
+ /// Returns the type `T` if [type] is `FutureOr<T>`. Returns `null` otherwise.
+ Type? getFutureOrTypeArgument(Type type);
+
+ /// Returns the non-nullable type `Future<T>` for [type] `T`.
+ Type instantiateFuture(Type type);
+
+ /// Returns a map of the field names and corresponding types available on
+ /// [type]. For an interface type, these are the fields and getters, and for
+ /// record types these are the record fields.
+ Map<Key, Type> getFieldTypes(Type type);
+
+ /// Returns the value type `V` if [type] implements `Map<K, V>` or `null`
+ /// otherwise.
+ Type? getMapValueType(Type type);
+
+ /// Returns the element type `E` if [type] implements `List<E>` or `null`
+ /// otherwise.
+ Type? getListElementType(Type type);
+
+ /// Returns the list type `List<E>` if [type] implements `List<E>` or `null`
+ /// otherwise.
+ Type? getListType(Type type);
+
+ /// Returns a human-readable representation of the [type].
+ String typeToString(Type type);
+
+ /// Returns `true` if [type] has a simple name that can be used as the type
+ /// of an object pattern.
+ bool hasSimpleName(Type type);
+}
+
+/// Interface for looking up fields and their corresponding [StaticType]s of
+/// a given type.
+abstract class FieldLookup<Type extends Object> {
+ /// Returns a map of the field names and corresponding [StaticType]s available
+ /// on [type]. For an interface type, these are the fields and getters, and
+ /// for record types these are the record fields.
+ Map<Key, StaticType> getFieldTypes(Type type);
+
+ StaticType? getAdditionalFieldType(Type type, Key key);
+}
+
+/// Cache used for computing [StaticType]s used for exhaustiveness checking.
+///
+/// This implementation is shared between analyzer and CFE, and implemented
+/// using the analyzer/CFE implementations of [TypeOperations],
+/// [EnumOperations], and [SealedClassOperations].
+class ExhaustivenessCache<
+ Type extends Object,
+ Class extends Object,
+ EnumClass extends Object,
+ EnumElement extends Object,
+ EnumElementValue extends Object>
+ implements FieldLookup<Type>, ObjectFieldLookup {
+ final TypeOperations<Type> typeOperations;
+ final EnumOperations<Type, EnumClass, EnumElement, EnumElementValue>
+ enumOperations;
+ final SealedClassOperations<Type, Class> _sealedClassOperations;
+
+ /// Cache for [EnumInfo] for enum classes.
+ Map<EnumClass, EnumInfo<Type, EnumClass, EnumElement, EnumElementValue>>
+ _enumInfo = {};
+
+ /// Cache for [SealedClassInfo] for sealed classes.
+ Map<Class, SealedClassInfo<Type, Class>> _sealedClassInfo = {};
+
+ /// Cache for [UniqueStaticType]s.
+ Map<Object, StaticType> _uniqueTypeMap = {};
+
+ /// Cache for the [StaticType] for `bool`.
+ late BoolStaticType _boolStaticType =
+ new BoolStaticType(typeOperations, this, typeOperations.boolType);
+
+ /// Cache for [StaticType]s for fields available on a [Type].
+ Map<Type, Map<Key, StaticType>> _fieldCache = {};
+
+ ExhaustivenessCache(
+ this.typeOperations, this.enumOperations, this._sealedClassOperations);
+
+ /// Returns the [EnumInfo] for [enumClass].
+ EnumInfo<Type, EnumClass, EnumElement, EnumElementValue> _getEnumInfo(
+ EnumClass enumClass) {
+ return _enumInfo[enumClass] ??=
+ new EnumInfo(typeOperations, this, enumOperations, enumClass);
+ }
+
+ /// Returns the [SealedClassInfo] for [sealedClass].
+ SealedClassInfo<Type, Class> _getSealedClassInfo(Class sealedClass) {
+ return _sealedClassInfo[sealedClass] ??=
+ new SealedClassInfo(_sealedClassOperations, sealedClass);
+ }
+
+ /// Returns the [StaticType] for the boolean [value].
+ StaticType getBoolValueStaticType(bool value) {
+ return value ? _boolStaticType.trueType : _boolStaticType.falseType;
+ }
+
+ /// Returns the [StaticType] for [type].
+ StaticType getStaticType(Type type) {
+ if (typeOperations.isNeverType(type)) {
+ return StaticType.neverType;
+ } else if (typeOperations.isNullType(type)) {
+ return StaticType.nullType;
+ } else if (typeOperations.isNonNullableObject(type)) {
+ return StaticType.nonNullableObject;
+ } else if (typeOperations.isNullableObject(type) ||
+ typeOperations.isDynamic(type)) {
+ return StaticType.nullableObject;
+ }
+
+ StaticType staticType;
+ Type nonNullable = typeOperations.getNonNullable(type);
+ if (typeOperations.isBoolType(nonNullable)) {
+ staticType = _boolStaticType;
+ } else if (typeOperations.isRecordType(nonNullable)) {
+ staticType = new RecordStaticType(typeOperations, this, nonNullable);
+ } else {
+ Type? futureOrTypeArgument =
+ typeOperations.getFutureOrTypeArgument(nonNullable);
+ if (futureOrTypeArgument != null) {
+ StaticType typeArgument = getStaticType(futureOrTypeArgument);
+ StaticType futureType = getStaticType(
+ typeOperations.instantiateFuture(futureOrTypeArgument));
+ staticType = new FutureOrStaticType(
+ typeOperations, this, nonNullable, typeArgument, futureType);
+ } else {
+ EnumClass? enumClass = enumOperations.getEnumClass(nonNullable);
+ if (enumClass != null) {
+ staticType = new EnumStaticType(
+ typeOperations, this, nonNullable, _getEnumInfo(enumClass));
+ } else {
+ Class? sealedClass =
+ _sealedClassOperations.getSealedClass(nonNullable);
+ if (sealedClass != null) {
+ staticType = new SealedClassStaticType(
+ typeOperations,
+ this,
+ nonNullable,
+ this,
+ _sealedClassOperations,
+ _getSealedClassInfo(sealedClass));
+ } else {
+ Type? listType = typeOperations.getListType(nonNullable);
+ if (listType == nonNullable) {
+ staticType =
+ new ListTypeStaticType(typeOperations, this, nonNullable);
+ } else {
+ staticType =
+ new TypeBasedStaticType(typeOperations, this, nonNullable);
+ }
+ }
+ }
+ }
+ }
+ if (typeOperations.isNullable(type)) {
+ staticType = staticType.nullable;
+ }
+ return staticType;
+ }
+
+ /// Returns the [StaticType] for the [enumElementValue] declared by
+ /// [enumClass].
+ StaticType getEnumElementStaticType(
+ EnumClass enumClass, EnumElementValue enumElementValue) {
+ return _getEnumInfo(enumClass).getEnumElement(enumElementValue);
+ }
+
+ /// Creates a new unique [StaticType].
+ StaticType getUnknownStaticType() {
+ // The unknown static type should be based on the nullable `Object`, since
+ // even though it _might_ be `null`, using the nullable `Object` here would
+ // mean that it _does_ include `null`, and we need this type to only cover
+ // itself.
+ return getUniqueStaticType<Object>(
+ typeOperations.nonNullableObjectType, new Object(), '?');
+ }
+
+ /// Returns a [StaticType] of the given [type] with the given
+ /// [textualRepresentation] that unique identifies the [uniqueValue].
+ ///
+ /// This is used for constants that are neither bool nor enum values.
+ StaticType getUniqueStaticType<Identity extends Object>(
+ Type type, Identity uniqueValue, String textualRepresentation) {
+ Type nonNullable = typeOperations.getNonNullable(type);
+ StaticType staticType = _uniqueTypeMap[uniqueValue] ??=
+ new ValueStaticType<Type, Identity>(
+ typeOperations,
+ this,
+ nonNullable,
+ new IdentityRestriction<Identity>(uniqueValue),
+ textualRepresentation);
+ if (typeOperations.isNullable(type)) {
+ staticType = staticType.nullable;
+ }
+ return staticType;
+ }
+
+ /// Returns a [StaticType] of the list [type] with the given [identity] .
+ StaticType getListStaticType(Type type, ListTypeIdentity<Type> identity) {
+ Type nonNullable = typeOperations.getNonNullable(type);
+ StaticType staticType = _uniqueTypeMap[identity] ??=
+ new ListPatternStaticType(
+ typeOperations, this, nonNullable, identity, identity.toString());
+ if (typeOperations.isNullable(type)) {
+ staticType = staticType.nullable;
+ }
+ return staticType;
+ }
+
+ /// Returns a [StaticType] of the map [type] with the given [identity] .
+ StaticType getMapStaticType(Type type, MapTypeIdentity<Type> identity) {
+ Type nonNullable = typeOperations.getNonNullable(type);
+ StaticType staticType = _uniqueTypeMap[identity] ??=
+ new MapPatternStaticType(
+ typeOperations, this, nonNullable, identity, identity.toString());
+ if (typeOperations.isNullable(type)) {
+ staticType = staticType.nullable;
+ }
+ return staticType;
+ }
+
+ @override
+ Map<Key, StaticType> getFieldTypes(Type type) {
+ Map<Key, StaticType>? fields = _fieldCache[type];
+ if (fields == null) {
+ _fieldCache[type] = fields = {};
+ for (MapEntry<Key, Type> entry
+ in typeOperations.getFieldTypes(type).entries) {
+ fields[entry.key] = getStaticType(entry.value);
+ }
+ }
+ return fields;
+ }
+
+ @override
+ StaticType? getAdditionalFieldType(Type type, Key key) {
+ if (key is MapKey) {
+ Type? valueType = typeOperations.getMapValueType(type);
+ if (valueType != null) {
+ return getStaticType(valueType);
+ }
+ } else if (key is HeadKey || key is TailKey) {
+ Type? elementType = typeOperations.getListElementType(type);
+ if (elementType != null) {
+ return getStaticType(elementType);
+ }
+ } else if (key is RestKey) {
+ Type? listType = typeOperations.getListType(type);
+ if (listType != null) {
+ return getStaticType(listType);
+ }
+ } else {
+ return getObjectFieldType(key);
+ }
+ return null;
+ }
+
+ @override
+ StaticType? getObjectFieldType(Key key) {
+ return getFieldTypes(typeOperations.nonNullableObjectType)[key];
+ }
+}
+
+/// Mixin for creating [Space]s from [Pattern]s.
+mixin SpaceCreator<Pattern extends Object, Type extends Object> {
+ TypeOperations<Type> get typeOperations;
+
+ ObjectFieldLookup get objectFieldLookup;
+
+ /// Creates a [StaticType] for an unknown type.
+ ///
+ /// This is used when the type of the pattern is unknown or can't be
+ /// represented as a [StaticType]. This type is unique and ensures that it
+ /// is neither matches anything nor is matched by anything.
+ StaticType createUnknownStaticType();
+
+ /// Creates the [StaticType] for [type].
+ StaticType createStaticType(Type type);
+
+ /// Creates the [StaticType] for [type] restricted by the [contextType].
+ /// If [nonNull] is `true`, the created type is non-nullable.
+ StaticType _createStaticTypeWithContext(StaticType contextType, Type type,
+ {required bool nonNull}) {
+ StaticType staticType = createStaticType(type);
+ if (contextType.isSubtypeOf(staticType)) {
+ staticType = contextType;
+ }
+ if (nonNull && staticType is NullableStaticType) {
+ staticType = staticType.underlying;
+ }
+ return staticType;
+ }
+
+ /// Creates the [StaticType] for the list [type] with the given [identity].
+ StaticType createListType(Type type, ListTypeIdentity<Type> identity);
+
+ /// Creates the [StaticType] for the map [type] with the given [identity].
+ StaticType createMapType(Type type, MapTypeIdentity<Type> identity);
+
+ /// Creates the [Space] for [pattern] at the given [path].
+ ///
+ /// The [contextType] is the [StaticType] in which the pattern match is
+ /// performed. This is used to the restrict type of the created [Space] to
+ /// the types allowed by the context. For instance `Object(:var hashCode)` is
+ /// in itself unrestricted and would yield the top space for matching
+ /// `var hashCode`. Using the [contextType] `int`, as given by the type of
+ /// the `Object.hashCode`, the created space is all `int` values rather than
+ /// all values.
+ ///
+ /// If [nonNull] is `true`, the space is implicitly non-nullable.
+ Space dispatchPattern(Path path, StaticType contextType, Pattern pattern,
+ {required bool nonNull});
+
+ /// Creates the root space for [pattern].
+ Space createRootSpace(StaticType contextType, Pattern pattern,
+ {required bool hasGuard}) {
+ if (hasGuard) {
+ return createUnknownSpace(const Path.root());
+ } else {
+ return dispatchPattern(const Path.root(), contextType, pattern,
+ nonNull: false);
+ }
+ }
+
+ /// Creates the [Space] at [path] for a variable pattern of the declared
+ /// [type].
+ ///
+ /// If [nonNull] is `true`, the space is implicitly non-nullable.
+ Space createVariableSpace(Path path, StaticType contextType, Type type,
+ {required bool nonNull}) {
+ StaticType staticType =
+ _createStaticTypeWithContext(contextType, type, nonNull: nonNull);
+ return new Space(path, staticType);
+ }
+
+ /// Creates the [Space] at [path] for an object pattern of the required [type]
+ /// and [fieldPatterns].
+ ///
+ /// If [nonNull] is `true`, the space is implicitly non-nullable.
+ Space createObjectSpace(Path path, StaticType contextType, Type type,
+ Map<String, Pattern> fieldPatterns,
+ {required bool nonNull}) {
+ StaticType staticType =
+ _createStaticTypeWithContext(contextType, type, nonNull: nonNull);
+ Map<Key, Space> fields = <Key, Space>{};
+ for (MapEntry<String, Pattern> entry in fieldPatterns.entries) {
+ Key key = new NameKey(entry.key);
+ StaticType fieldType = staticType.getField(objectFieldLookup, key) ??
+ StaticType.nullableObject;
+ fields[key] = dispatchPattern(path.add(key), fieldType, entry.value,
+ nonNull: false);
+ }
+ return new Space(path, staticType, fields: fields);
+ }
+
+ /// Creates the [Space] at [path] for a record pattern of the required [type],
+ /// [positionalFields], and [namedFields].
+ Space createRecordSpace(Path path, StaticType contextType, Type recordType,
+ List<Pattern> positionalFields, Map<String, Pattern> namedFields) {
+ StaticType staticType =
+ _createStaticTypeWithContext(contextType, recordType, nonNull: true);
+ Map<Key, Space> fields = <Key, Space>{};
+ for (int index = 0; index < positionalFields.length; index++) {
+ Key key = new RecordIndexKey(index);
+ StaticType fieldType = staticType.getField(objectFieldLookup, key) ??
+ StaticType.nullableObject;
+ fields[key] = dispatchPattern(
+ path.add(key), fieldType, positionalFields[index],
+ nonNull: false);
+ }
+ for (MapEntry<String, Pattern> entry in namedFields.entries) {
+ Key key = new RecordNameKey(entry.key);
+ StaticType fieldType = staticType.getField(objectFieldLookup, key) ??
+ StaticType.nullableObject;
+ fields[key] = dispatchPattern(path.add(key), fieldType, entry.value,
+ nonNull: false);
+ }
+ return new Space(path, staticType, fields: fields);
+ }
+
+ /// Creates the [Space] at [path] for a wildcard pattern with the declared
+ /// [type].
+ ///
+ /// If [nonNull] is `true`, the space is implicitly non-nullable.
+ Space createWildcardSpace(Path path, StaticType contextType, Type? type,
+ {required bool nonNull}) {
+ if (type == null) {
+ StaticType staticType = contextType;
+ if (nonNull && staticType is NullableStaticType) {
+ staticType = staticType.underlying;
+ }
+ return new Space(path, staticType);
+ } else {
+ StaticType staticType =
+ _createStaticTypeWithContext(contextType, type, nonNull: nonNull);
+ return new Space(path, staticType);
+ }
+ }
+
+ /// Creates the [Space] at [path] for a relational pattern.
+ Space createRelationalSpace(Path path) {
+ // This pattern do not add to the exhaustiveness coverage.
+ return createUnknownSpace(path);
+ }
+
+ /// Creates the [Space] at [path] for a cast pattern with the given
+ /// [subPattern].
+ ///
+ /// If [nonNull] is `true`, the space is implicitly non-nullable.
+ Space createCastSpace(
+ Path path, StaticType contextType, Type type, Pattern subPattern,
+ {required bool nonNull}) {
+ Space space =
+ dispatchPattern(path, contextType, subPattern, nonNull: nonNull);
+ StaticType castType = createStaticType(type);
+ if (castType.isSubtypeOf(contextType) && contextType.isSealed) {
+ for (StaticType subtype in expandSealedSubtypes(contextType, const {})) {
+ // If [subtype] is a subtype of [castType] it will not throw and must
+ // be handled by [subPattern]. For instance
+ //
+ // sealed class S {}
+ // sealed class X extends S {}
+ // class A extends X {}
+ // method(S s) => switch (s) {
+ // A() as X => 0,
+ // }
+ //
+ // If [castType] is a subtype of [subtype] it might not throw but still
+ // not handle all values of the [subtype].
+ //
+ // sealed class S {}
+ // class A extends S {}
+ // class X extends A {
+ // int field;
+ // X(this.field);
+ // }
+ // method(S s) => switch (s) {
+ // X(field: 42) as X => 0,
+ // }
+ //
+ if (!castType.isSubtypeOf(subtype) && !subtype.isSubtypeOf(castType)) {
+ // Otherwise the cast implicitly handles [subtype].
+ space = space.union(new Space(path, subtype));
+ }
+ }
+ }
+ return space;
+ }
+
+ /// Creates the [Space] at [path] for a null check pattern with the given
+ /// [subPattern].
+ Space createNullCheckSpace(
+ Path path, StaticType contextType, Pattern subPattern) {
+ return dispatchPattern(path, contextType, subPattern, nonNull: true);
+ }
+
+ /// Creates the [Space] at [path] for a null assert pattern with the given
+ /// [subPattern].
+ Space createNullAssertSpace(
+ Path path, StaticType contextType, Pattern subPattern) {
+ Space space = dispatchPattern(path, contextType, subPattern, nonNull: true);
+ return space.union(new Space(path, StaticType.nullType));
+ }
+
+ /// Creates the [Space] at [path] for a logical or pattern with the given
+ /// [left] and [right] subpatterns.
+ ///
+ /// If [nonNull] is `true`, the space is implicitly non-nullable.
+ Space createLogicalOrSpace(
+ Path path, StaticType contextType, Pattern left, Pattern right,
+ {required bool nonNull}) {
+ Space aSpace = dispatchPattern(path, contextType, left, nonNull: nonNull);
+ Space bSpace = dispatchPattern(path, contextType, right, nonNull: nonNull);
+ return aSpace.union(bSpace);
+ }
+
+ /// Creates the [Space] at [path] for a logical and pattern with the given
+ /// [left] and [right] subpatterns.
+ ///
+ /// If [nonNull] is `true`, the space is implicitly non-nullable.
+ Space createLogicalAndSpace(
+ Path path, StaticType contextType, Pattern left, Pattern right,
+ {required bool nonNull}) {
+ Space aSpace = dispatchPattern(path, contextType, left, nonNull: nonNull);
+ Space bSpace = dispatchPattern(path, contextType, right, nonNull: nonNull);
+ return _createSpaceIntersection(path, aSpace, bSpace);
+ }
+
+ /// Creates the [Space] at [path] for a list pattern.
+ Space createListSpace(Path path,
+ {required Type type,
+ required Type elementType,
+ required List<Pattern> headElements,
+ required Pattern? restElement,
+ required List<Pattern> tailElements,
+ required bool hasRest,
+ required bool hasExplicitTypeArgument}) {
+ int headSize = headElements.length;
+ int tailSize = tailElements.length;
+
+ String typeArgumentText;
+ if (hasExplicitTypeArgument) {
+ StringBuffer sb = new StringBuffer();
+ sb.write('<');
+ sb.write(typeOperations.typeToString(elementType));
+ sb.write('>');
+ typeArgumentText = sb.toString();
+ } else {
+ typeArgumentText = '';
+ }
+
+ ListTypeIdentity<Type> identity = new ListTypeIdentity(
+ elementType, typeArgumentText,
+ size: headSize + tailSize, hasRest: hasRest);
+
+ StaticType staticType = createListType(type, identity);
+
+ Map<Key, Space> additionalFields = {};
+ for (int index = 0; index < headSize; index++) {
+ Key key = new HeadKey(index);
+ StaticType fieldType =
+ staticType.getAdditionalField(key) ?? StaticType.nullableObject;
+ additionalFields[key] = dispatchPattern(
+ path.add(key), fieldType, headElements[index],
+ nonNull: false);
+ }
+ if (hasRest) {
+ Key key = new RestKey(headSize, tailSize);
+ StaticType fieldType =
+ staticType.getAdditionalField(key) ?? StaticType.nullableObject;
+ if (restElement != null) {
+ additionalFields[key] = dispatchPattern(
+ path.add(key), fieldType, restElement,
+ nonNull: false);
+ } else {
+ additionalFields[key] = new Space(path.add(key), fieldType);
+ }
+ }
+ for (int index = 0; index < tailSize; index++) {
+ Key key = new TailKey(index);
+ StaticType fieldType =
+ staticType.getAdditionalField(key) ?? StaticType.nullableObject;
+ additionalFields[key] = dispatchPattern(path.add(key), fieldType,
+ tailElements[tailElements.length - index - 1],
+ nonNull: false);
+ }
+ return new Space(path, staticType, additionalFields: additionalFields);
+ }
+
+ /// Creates the [Space] at [path] for a map pattern.
+ Space createMapSpace(Path path,
+ {required Type type,
+ required Type keyType,
+ required Type valueType,
+ required Map<MapKey, Pattern> entries,
+ required bool hasRest,
+ required bool hasExplicitTypeArguments}) {
+ String typeArgumentsText;
+ if (hasExplicitTypeArguments) {
+ StringBuffer sb = new StringBuffer();
+ sb.write('<');
+ sb.write(typeOperations.typeToString(keyType));
+ sb.write(', ');
+ sb.write(typeOperations.typeToString(valueType));
+ sb.write('>');
+ typeArgumentsText = sb.toString();
+ } else {
+ typeArgumentsText = '';
+ }
+
+ MapTypeIdentity<Type> identity = new MapTypeIdentity(
+ keyType, valueType, entries.keys.toSet(), typeArgumentsText,
+ hasRest: hasRest);
+ StaticType staticType = createMapType(type, identity);
+
+ Map<Key, Space> additionalFields = {};
+ for (MapEntry<Key, Pattern> entry in entries.entries) {
+ Key key = entry.key;
+ StaticType fieldType =
+ staticType.getAdditionalField(key) ?? StaticType.nullableObject;
+ additionalFields[key] = dispatchPattern(
+ path.add(key), fieldType, entry.value,
+ nonNull: false);
+ }
+ return new Space(path, staticType, additionalFields: additionalFields);
+ }
+
+ /// Creates the [Space] at [path] for a pattern with unknown space.
+ ///
+ /// This is used when the space of the pattern is unknown or can't be
+ /// represented precisely as a union of [SingleSpace]s. This space is unique
+ /// and ensures that it is neither matches anything nor is matched by
+ /// anything.
+ Space createUnknownSpace(Path path) {
+ return new Space(path, createUnknownStaticType());
+ }
+
+ /// Creates an approximation of the intersection of the single spaces [a] and
+ /// [b].
+ SingleSpace? _createSingleSpaceIntersection(
+ Path path, SingleSpace a, SingleSpace b) {
+ StaticType? type;
+ if (a.type.isSubtypeOf(b.type)) {
+ type = a.type;
+ } else if (b.type.isSubtypeOf(a.type)) {
+ type = b.type;
+ }
+ if (type == null) {
+ return null;
+ }
+ Map<Key, Space> fields = {};
+ for (MapEntry<Key, Space> entry in a.fields.entries) {
+ Key key = entry.key;
+ Space aSpace = entry.value;
+ Space? bSpace = b.fields[key];
+ if (bSpace != null) {
+ fields[key] = _createSpaceIntersection(path.add(key), aSpace, bSpace);
+ } else {
+ fields[key] = aSpace;
+ }
+ }
+ for (MapEntry<Key, Space> entry in b.fields.entries) {
+ Key key = entry.key;
+ fields[key] ??= entry.value;
+ }
+ return new SingleSpace(type, fields: fields);
+ }
+
+ /// Creates an approximation of the intersection of spaces [a] and [b].
+ Space _createSpaceIntersection(Path path, Space a, Space b) {
+ assert(
+ path == a.path, "Unexpected path. Expected $path, actual ${a.path}.");
+ assert(
+ path == b.path, "Unexpected path. Expected $path, actual ${b.path}.");
+ List<SingleSpace> singleSpaces = [];
+ bool hasUnknownSpace = false;
+ for (SingleSpace aSingleSpace in a.singleSpaces) {
+ for (SingleSpace bSingleSpace in b.singleSpaces) {
+ SingleSpace? space =
+ _createSingleSpaceIntersection(path, aSingleSpace, bSingleSpace);
+ if (space != null) {
+ singleSpaces.add(space);
+ } else {
+ hasUnknownSpace = true;
+ }
+ }
+ }
+ if (hasUnknownSpace) {
+ singleSpaces.add(new SingleSpace(createUnknownStaticType()));
+ }
+ return new Space.fromSingleSpaces(path, singleSpaces);
+ }
+}
diff --git a/_fe_analyzer_shared/lib/src/exhaustiveness/space.dart b/_fe_analyzer_shared/lib/src/exhaustiveness/space.dart
index 9a08e11..4179730 100644
--- a/_fe_analyzer_shared/lib/src/exhaustiveness/space.dart
+++ b/_fe_analyzer_shared/lib/src/exhaustiveness/space.dart
@@ -1,156 +1,120 @@
-// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-import 'equal.dart';
+import 'key.dart';
+import 'path.dart';
import 'static_type.dart';
-/// The main space for matching types and destructuring.
+/// The main pattern for matching types and destructuring.
///
/// It has a type which determines the type of values it contains. The type may
/// be [StaticType.nullableObject] to indicate that it doesn't filter by type.
///
-/// It may also contain zero or more named fields. The space then only contains
-/// values where the field values are contained by the corresponding field
-/// spaces.
-class ExtractSpace extends Space {
- /// The type of values the space matches.
+/// It may also contain zero or more named fields. The pattern then only matches
+/// values where the field values are matched by the corresponding field
+/// patterns.
+class SingleSpace {
+ static final SingleSpace empty = new SingleSpace(StaticType.neverType);
+
+ /// The type of values the pattern matches.
final StaticType type;
- /// Any field subspaces the space matches.
- final Map<String, Space> fields;
+ /// Any field subpatterns the pattern matches.
+ final Map<Key, Space> fields;
- ExtractSpace._(this.type, [this.fields = const {}]) : super._();
+ /// Additional fields for map/list semantics.
+ final Map<Key, Space> additionalFields;
- /// An [ExtractSpace] with no type and no fields contains all values.
+ SingleSpace(this.type,
+ {this.fields = const {}, this.additionalFields = const {}});
+
@override
- bool get isTop => type == StaticType.nullableObject && fields.isEmpty;
+ late final int hashCode = Object.hash(
+ type,
+ Object.hashAllUnordered(fields.keys),
+ Object.hashAllUnordered(fields.values));
+
+ @override
+ bool operator ==(Object other) {
+ if (identical(this, other)) return true;
+ if (other is! SingleSpace) return false;
+ if (type != other.type) return false;
+ if (fields.length != other.fields.length) return false;
+ if (fields.isNotEmpty) {
+ for (MapEntry<Key, Space> entry in fields.entries) {
+ if (entry.value != other.fields[entry.key]) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
@override
String toString() {
- if (isTop) return '()';
- if (this == Space.empty) return '∅';
-
- if (type.isRecord) {
- StringBuffer buffer = new StringBuffer();
- buffer.write('(');
- bool first = true;
- type.fields.forEach((String name, StaticType staticType) {
- if (!first) buffer.write(', ');
- // TODO(johnniwinther): Ensure using Dart syntax for positional fields.
- buffer.write('$name: ${fields[name] ?? staticType}');
- first = false;
- });
-
- buffer.write(')');
- return buffer.toString();
- } else {
- // If there are no fields, just show the type.
- if (fields.isEmpty) return type.name;
-
- StringBuffer buffer = new StringBuffer();
- buffer.write(type.name);
-
- buffer.write('(');
- bool first = true;
-
- fields.forEach((String name, Space space) {
- if (!first) buffer.write(', ');
- buffer.write('$name: $space');
- first = false;
- });
-
- buffer.write(')');
- return buffer.toString();
- }
+ return type.spaceToText(fields, additionalFields);
}
}
-// TODO(paulberry, rnystrom): List spaces.
+/// A set of runtime values encoded as a union of [SingleSpace]s.
+///
+/// This is used to support logical-or patterns without having to eagerly
+/// expand the subpatterns in the parent context.
+class Space {
+ /// The path of getters that led from the original matched value to value
+ /// matched by this pattern. Used to generate a human-readable witness.
+ final Path path;
-abstract class Space {
- /// The uninhabited space.
- static final Space empty = new Space(StaticType.neverType);
+ final List<SingleSpace> singleSpaces;
- /// The space containing everything.
- static final Space top = new Space(StaticType.nullableObject);
+ /// Create an empty space.
+ Space.empty(this.path) : singleSpaces = [SingleSpace.empty];
- /// The space containing only `null`.
- static final Space nullSpace = new Space(StaticType.nullType);
+ Space(Path path, StaticType type,
+ {Map<Key, Space> fields = const {},
+ Map<Key, Space> additionalFields = const {}})
+ : this._(path, [
+ new SingleSpace(type,
+ fields: fields, additionalFields: additionalFields)
+ ]);
- factory Space(StaticType type, [Map<String, Space> fields = const {}]) =>
- new ExtractSpace._(type, fields);
+ Space._(this.path, this.singleSpaces);
- factory Space.union(List<Space> arms) {
- // Simplify the arms if possible.
- List<ExtractSpace> allArms = <ExtractSpace>[];
+ factory Space.fromSingleSpaces(Path path, List<SingleSpace> singleSpaces) {
+ Set<SingleSpace> singleSpacesSet = {};
- void addSpace(ExtractSpace space) {
- // Discard duplicate arms. Duplicates can appear when working through a
- // series of cases that destructure multiple fields with different types.
- // Discarding the duplicates isn't necessary for correctness (a union with
- // redundant arms contains the same set of values), but improves
- // performance greatly. In the "sealed subtypes large T with all cases"
- // test, you end up with a union containing 2520 arms, 2488 are
- // duplicates. With this check, the largest union has only 5 arms.
- //
- // This is O(n^2) since we define only equality on spaces, but a real
- // implementation would likely define hash code too and then simply
- // create a hash set to merge duplicates in O(n) time.
- for (Space existing in allArms) {
- if (equal(existing, space, 'dedupe union')) return;
+ for (SingleSpace singleSpace in singleSpaces) {
+ // Discard empty space.
+ if (singleSpace == SingleSpace.empty) {
+ continue;
}
- allArms.add(space);
+ singleSpacesSet.add(singleSpace);
}
- for (Space space in arms) {
- // Discard empty arms.
- if (space == empty) continue;
-
- // Flatten unions. We don't need to flatten recursively since we always
- // go through this constructor to create unions. A UnionSpace will never
- // contain UnionSpaces.
- if (space is UnionSpace) {
- for (ExtractSpace arm in space.arms) {
- addSpace(arm);
- }
- } else {
- addSpace(space as ExtractSpace);
+ List<SingleSpace> singleSpacesList = singleSpacesSet.toList();
+ if (singleSpacesSet.isEmpty) {
+ singleSpacesList.add(SingleSpace.empty);
+ } else if (singleSpacesList.length == 2) {
+ if (singleSpacesList[0].type == StaticType.nullType &&
+ singleSpacesList[0].fields.isEmpty &&
+ singleSpacesList[1].fields.isEmpty) {
+ singleSpacesList = [new SingleSpace(singleSpacesList[1].type.nullable)];
+ } else if (singleSpacesList[1].type == StaticType.nullType &&
+ singleSpacesList[1].fields.isEmpty &&
+ singleSpacesList[0].fields.isEmpty) {
+ singleSpacesList = [new SingleSpace(singleSpacesList[0].type.nullable)];
}
}
-
- if (allArms.isEmpty) return empty;
- if (allArms.length == 1) return allArms.first;
- if (allArms.length == 2) {
- if (allArms[0].type == StaticType.nullType &&
- allArms[0].fields.isEmpty &&
- allArms[1].fields.isEmpty) {
- return new Space(allArms[1].type.nullable);
- } else if (allArms[1].type == StaticType.nullType &&
- allArms[1].fields.isEmpty &&
- allArms[0].fields.isEmpty) {
- return new Space(allArms[0].type.nullable);
- }
- }
- return new UnionSpace._(allArms);
+ return new Space._(path, singleSpacesList);
}
- Space._();
-
- /// An untyped record space with no fields matches all values and thus isn't
- /// very useful.
- bool get isTop => false;
-}
-
-/// A union of spaces. The space A|B contains all of the values of A and B.
-class UnionSpace extends Space {
- final List<ExtractSpace> arms;
-
- UnionSpace._(this.arms) : super._() {
- assert(arms.length > 1);
+ Space union(Space other) {
+ return new Space.fromSingleSpaces(
+ path, [...singleSpaces, ...other.singleSpaces]);
}
@override
- String toString() => arms.join('|');
+ String toString() => singleSpaces.join('|');
}
diff --git a/_fe_analyzer_shared/lib/src/exhaustiveness/static_type.dart b/_fe_analyzer_shared/lib/src/exhaustiveness/static_type.dart
index d2ea571..3280423 100644
--- a/_fe_analyzer_shared/lib/src/exhaustiveness/static_type.dart
+++ b/_fe_analyzer_shared/lib/src/exhaustiveness/static_type.dart
@@ -2,7 +2,9 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-// TODO(paulberry,rnystrom): Generics.
+import 'key.dart';
+import 'space.dart';
+import 'witness.dart';
/// A static type in the type system.
abstract class StaticType {
@@ -22,7 +24,21 @@
/// The static types of the fields this type exposes for record destructuring.
///
/// Includes inherited fields.
- Map<String, StaticType> get fields;
+ Map<Key, StaticType> get fields;
+
+ /// Returns the static type for the [name] in this static type, or `null` if
+ /// no such key exists.
+ ///
+ /// This is used to support implicit on the constant [StaticType]s
+ /// [nullableObject], [nonNullableObject], [nullType] and [neverType].
+ StaticType? getField(ObjectFieldLookup fieldLookup, Key key);
+
+ /// Returns the static type for the [key] in this static type, or `null` if
+ /// no such key exists.
+ ///
+ /// This is used to model keys in map patterns, and indices and ranges in list
+ /// patterns.
+ StaticType? getAdditionalField(Key key);
/// Returns `true` if this static type is a subtype of [other], taking the
/// nullability and subtyping relation into account.
@@ -50,7 +66,7 @@
/// Returns `true` if this is a record type.
///
- /// This is only used for print the type as part of a [Space].
+ /// This is only used for print the type as part of a [Witness].
bool get isRecord;
/// Returns the name of this static type.
@@ -61,8 +77,30 @@
/// Returns the nullable static type corresponding to this type.
StaticType get nullable;
+ /// Returns the non-nullable static type corresponding to this type.
+ StaticType get nonNullable;
+
/// The immediate subtypes of this type.
- Iterable<StaticType> get subtypes;
+ ///
+ /// The [keysOfInterest] of interest are the keys used in one of the case
+ /// rows. This is used to select how a `List` type should be divided into
+ /// subtypes that should be used for testing the exhaustiveness of a list.
+ Iterable<StaticType> getSubtypes(Set<Key> keysOfInterest);
+
+ /// Returns a textual representation of a single space consisting of this
+ /// type and the provided [fields] and [additionalFields].
+ String spaceToText(
+ Map<Key, Space> spaceFields, Map<Key, Space> additionalSpaceFields);
+
+ void witnessToText(StringBuffer buffer, FieldWitness witness,
+ Map<Key, FieldWitness> witnessFields);
+}
+
+mixin _ObjectFieldMixin on _BaseStaticType {
+ @override
+ StaticType? getField(ObjectFieldLookup fieldLookup, Key key) {
+ return fields[key] ?? fieldLookup.getObjectFieldType(key);
+ }
}
abstract class _BaseStaticType implements StaticType {
@@ -72,16 +110,76 @@
bool get isRecord => false;
@override
- Map<String, StaticType> get fields => const {};
+ Map<Key, StaticType> get fields => const {};
@override
- Iterable<StaticType> get subtypes => const [];
+ StaticType? getField(ObjectFieldLookup fieldLookup, Key name) {
+ return fields[name];
+ }
+
+ @override
+ StaticType? getAdditionalField(Key key) => null;
+
+ @override
+ Iterable<StaticType> getSubtypes(Set<Key> keysOfInterest) => const [];
+
+ @override
+ String spaceToText(
+ Map<Key, Space> spaceFields, Map<Key, Space> additionalSpaceFields) {
+ assert(additionalSpaceFields.isEmpty,
+ "Additional fields not supported in ${runtimeType}.");
+ if (this == StaticType.nullableObject && spaceFields.isEmpty) return '()';
+ if (this == StaticType.neverType && spaceFields.isEmpty) return '∅';
+
+ // If there are no fields, just show the type.
+ if (spaceFields.isEmpty) return name;
+
+ StringBuffer buffer = new StringBuffer();
+ buffer.write(name);
+
+ buffer.write('(');
+ bool first = true;
+
+ spaceFields.forEach((Key key, Space space) {
+ if (!first) buffer.write(', ');
+ buffer.write('${key.name}: $space');
+ first = false;
+ });
+
+ buffer.write(')');
+ return buffer.toString();
+ }
+
+ @override
+ void witnessToText(StringBuffer buffer, FieldWitness witness,
+ Map<Key, FieldWitness> witnessFields) {
+ if (this == StaticType.nullableObject && witnessFields.isEmpty) {
+ buffer.write('_');
+ } else if (this == StaticType.nullType && witnessFields.isEmpty) {
+ buffer.write('null');
+ } else {
+ buffer.write(name);
+ buffer.write('(');
+ if (witnessFields.isNotEmpty) {
+ String comma = '';
+ for (MapEntry<Key, FieldWitness> entry in witnessFields.entries) {
+ buffer.write(comma);
+ comma = ', ';
+
+ buffer.write(entry.key.name);
+ buffer.write(': ');
+ entry.value.witnessToText(buffer);
+ }
+ }
+ buffer.write(')');
+ }
+ }
@override
String toString() => name;
}
-class _NonNullableObject extends _BaseStaticType {
+class _NonNullableObject extends _BaseStaticType with _ObjectFieldMixin {
const _NonNullableObject();
@override
@@ -98,9 +196,12 @@
@override
StaticType get nullable => StaticType.nullableObject;
+
+ @override
+ StaticType get nonNullable => this;
}
-class _NeverType extends _BaseStaticType {
+class _NeverType extends _BaseStaticType with _ObjectFieldMixin {
const _NeverType();
@override
@@ -117,9 +218,12 @@
@override
StaticType get nullable => StaticType.nullType;
+
+ @override
+ StaticType get nonNullable => this;
}
-class _NullType extends NullableStaticType {
+class _NullType extends NullableStaticType with _ObjectFieldMixin {
const _NullType(super.underlying);
@override
@@ -129,7 +233,7 @@
}
@override
- Iterable<StaticType> get subtypes {
+ Iterable<StaticType> getSubtypes(Set<Key> keysOfInterest) {
// Avoid splitting into [nullType] and [neverType].
return const [];
}
@@ -138,7 +242,7 @@
String get name => 'Null';
}
-class NullableStaticType extends _BaseStaticType {
+class NullableStaticType extends _BaseStaticType with _ObjectFieldMixin {
final StaticType underlying;
const NullableStaticType(this.underlying);
@@ -147,7 +251,8 @@
bool get isSealed => true;
@override
- Iterable<StaticType> get subtypes => [underlying, StaticType.nullType];
+ Iterable<StaticType> getSubtypes(Set<Key> keysOfInterest) =>
+ [underlying, StaticType.nullType];
@override
bool isSubtypeOf(StaticType other) {
@@ -163,6 +268,9 @@
StaticType get nullable => this;
@override
+ StaticType get nonNullable => underlying;
+
+ @override
int get hashCode => underlying.hashCode * 11;
@override
@@ -177,6 +285,9 @@
late final StaticType nullable = new NullableStaticType(this);
@override
+ StaticType get nonNullable => this;
+
+ @override
bool isSubtypeOf(StaticType other) {
if (this == other) return true;
@@ -192,7 +303,15 @@
return isSubtypeOf(other.underlying);
}
- return isSubtypeOfInternal(other);
+ if (isSubtypeOfInternal(other)) {
+ return true;
+ }
+
+ if (other is WrappedStaticType) {
+ return isSubtypeOf(other.wrappedType) && isSubtypeOf(other.impliedType);
+ }
+
+ return false;
}
bool isSubtypeOfInternal(StaticType other);
@@ -200,3 +319,58 @@
@override
String toString() => name;
}
+
+/// Static type the behaves like [wrappedType] but is also a subtype of
+/// [impliedType].
+class WrappedStaticType extends NonNullableStaticType {
+ final StaticType wrappedType;
+ final StaticType impliedType;
+
+ WrappedStaticType(this.wrappedType, this.impliedType);
+
+ @override
+ Map<Key, StaticType> get fields => wrappedType.fields;
+
+ @override
+ bool get isRecord => wrappedType.isRecord;
+
+ @override
+ bool get isSealed => wrappedType.isSealed;
+
+ @override
+ String get name => wrappedType.name;
+
+ @override
+ Iterable<StaticType> getSubtypes(Set<Key> keysOfInterest) => wrappedType
+ .getSubtypes(keysOfInterest)
+ .map((e) => new WrappedStaticType(e, impliedType));
+
+ @override
+ bool isSubtypeOfInternal(StaticType other) {
+ return wrappedType.isSubtypeOf(other) || impliedType.isSubtypeOf(other);
+ }
+
+ @override
+ int get hashCode => Object.hash(wrappedType, impliedType);
+
+ @override
+ bool operator ==(other) {
+ if (identical(this, other)) return true;
+ return other is WrappedStaticType &&
+ wrappedType == other.wrappedType &&
+ impliedType == other.impliedType;
+ }
+
+ @override
+ void witnessToText(StringBuffer buffer, FieldWitness witness,
+ Map<Key, FieldWitness> witnessFields) {
+ return wrappedType.witnessToText(buffer, witness, witnessFields);
+ }
+}
+
+/// Interface for accessing the members defined on `Object`.
+abstract class ObjectFieldLookup {
+ /// Returns the [StaticType] for the member with the given [key] defined on
+ /// `Object`, or `null` none exists.
+ StaticType? getObjectFieldType(Key key);
+}
diff --git a/_fe_analyzer_shared/lib/src/exhaustiveness/static_types.dart b/_fe_analyzer_shared/lib/src/exhaustiveness/static_types.dart
deleted file mode 100644
index e637152..0000000
--- a/_fe_analyzer_shared/lib/src/exhaustiveness/static_types.dart
+++ /dev/null
@@ -1,513 +0,0 @@
-// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-import 'package:_fe_analyzer_shared/src/exhaustiveness/static_type.dart';
-
-/// Interface implemented by analyze/CFE to support type operations need for the
-/// shared [StaticType]s.
-abstract class TypeOperations<Type extends Object> {
- /// Returns the type for `Object`.
- Type get nullableObjectType;
-
- /// Returns `true` if [s] is a subtype of [t].
- bool isSubtypeOf(Type s, Type t);
-
- /// Returns `true` if [type] is a potentially nullable type.
- bool isNullable(Type type);
-
- /// Returns the non-nullable type corresponding to [type]. For instance
- /// `Foo` for `Foo?`. If [type] is already non-nullable, it itself is
- /// returned.
- Type getNonNullable(Type type);
-
- /// Returns `true` if [type] is the `Null` type.
- bool isNullType(Type type);
-
- /// Returns `true` if [type] is the `Never` type.
- bool isNeverType(Type type);
-
- /// Returns `true` if [type] is the `Object?` type.
- bool isNullableObject(Type type);
-
- /// Returns `true` if [type] is the `Object` type.
- bool isNonNullableObject(Type type);
-
- /// Returns `true` if [type] is the `bool` type.
- bool isBoolType(Type type);
-
- /// Returns the `bool` type.
- Type get boolType;
-
- /// Returns `true` if [type] is a record type.
- bool isRecordType(Type type);
-
- /// Returns a map of the field names and corresponding types available on
- /// [type]. For an interface type, these are the fields and getters, and for
- /// record types these are the record fields.
- Map<String, Type> getFieldTypes(Type type);
-
- /// Returns a human-readable representation of the [type].
- String typeToString(Type type);
-}
-
-/// Interface implemented by analyzer/CFE to support [StaticType]s for enums.
-abstract class EnumOperations<Type extends Object, EnumClass extends Object,
- EnumElement extends Object, EnumElementValue extends Object> {
- /// Returns the enum class declaration for the [type] or `null` if
- /// [type] is not an enum type.
- EnumClass? getEnumClass(Type type);
-
- /// Returns the enum elements defined by [enumClass].
- Iterable<EnumElement> getEnumElements(EnumClass enumClass);
-
- /// Returns the value defined by the [enumElement]. The encoding is specific
- /// the implementation of this interface but must ensure constant value
- /// identity.
- EnumElementValue getEnumElementValue(EnumElement enumElement);
-
- /// Returns the declared name of the [enumElement].
- String getEnumElementName(EnumElement enumElement);
-
- /// Returns the static type of the [enumElement].
- Type getEnumElementType(EnumElement enumElement);
-}
-
-/// Interface implemented by analyzer/CFE to support [StaticType]s for sealed
-/// classes.
-abstract class SealedClassOperations<Type extends Object,
- Class extends Object> {
- /// Returns the sealed class declaration for [type] or `null` if [type] is not
- /// a sealed class type.
- Class? getSealedClass(Type type);
-
- /// Returns the direct subclasses of [sealedClass] that either extend,
- /// implement or mix it in.
- List<Class> getDirectSubclasses(Class sealedClass);
-
- /// Returns the instance of [subClass] that implements [sealedClassType].
- ///
- /// `null` might be returned if [subClass] cannot implement [sealedClassType].
- /// For instance
- ///
- /// sealed class A<T> {}
- /// class B<T> extends A<T> {}
- /// class C extends A<int> {}
- ///
- /// here `C` has no implementation of `A<String>`.
- ///
- /// It is assumed that `TypeOperations.isSealedClass` is `true` for
- /// [sealedClassType] and that [subClass] is in `getDirectSubclasses` for
- /// `getSealedClass` of [sealedClassType].
- ///
- // TODO(johnniwinther): What should this return for generic types?
- Type? getSubclassAsInstanceOf(Class subClass, Type sealedClassType);
-}
-
-/// Interface for looking up fields and their corresponding [StaticType]s of
-/// a given type.
-abstract class FieldLookup<Type extends Object> {
- /// Returns a map of the field names and corresponding [StaticType]s available
- /// on [type]. For an interface type, these are the fields and getters, and
- /// for record types these are the record fields.
- Map<String, StaticType> getFieldTypes(Type type);
-}
-
-/// Cache used for computing [StaticType]s used for exhaustiveness checking.
-///
-/// This implementation is shared between analyzer and CFE, and implemented
-/// using the analyzer/CFE implementations of [TypeOperations],
-/// [EnumOperations], and [SealedClassOperations].
-class ExhaustivenessCache<
- Type extends Object,
- Class extends Object,
- EnumClass extends Object,
- EnumElement extends Object,
- EnumElementValue extends Object> implements FieldLookup<Type> {
- final TypeOperations<Type> _typeOperations;
- final EnumOperations<Type, EnumClass, EnumElement, EnumElementValue>
- enumOperations;
- final SealedClassOperations<Type, Class> _sealedClassOperations;
-
- /// Cache for [EnumInfo] for enum classes.
- Map<EnumClass, EnumInfo<Type, EnumClass, EnumElement, EnumElementValue>>
- _enumInfo = {};
-
- /// Cache for [SealedClassInfo] for sealed classes.
- Map<Class, SealedClassInfo<Type, Class>> _sealedClassInfo = {};
-
- /// Cache for [UniqueStaticType]s.
- Map<Object, StaticType> _uniqueTypeMap = {};
-
- /// Cache for the [StaticType] for `bool`.
- late BoolStaticType _boolStaticType =
- new BoolStaticType(_typeOperations, this, _typeOperations.boolType);
-
- /// Cache for [StaticType]s for fields available on a [Type].
- Map<Type, Map<String, StaticType>> _fieldCache = {};
-
- ExhaustivenessCache(
- this._typeOperations, this.enumOperations, this._sealedClassOperations);
-
- /// Returns the [EnumInfo] for [enumClass].
- EnumInfo<Type, EnumClass, EnumElement, EnumElementValue> _getEnumInfo(
- EnumClass enumClass) {
- return _enumInfo[enumClass] ??=
- new EnumInfo(_typeOperations, this, enumOperations, enumClass);
- }
-
- /// Returns the [SealedClassInfo] for [sealedClass].
- SealedClassInfo<Type, Class> _getSealedClassInfo(Class sealedClass) {
- return _sealedClassInfo[sealedClass] ??=
- new SealedClassInfo(_sealedClassOperations, sealedClass);
- }
-
- /// Returns the [StaticType] for the boolean [value].
- StaticType getBoolValueStaticType(bool value) {
- return value ? _boolStaticType.trueType : _boolStaticType.falseType;
- }
-
- /// Returns the [StaticType] for [type].
- StaticType getStaticType(Type type) {
- if (_typeOperations.isNeverType(type)) {
- return StaticType.neverType;
- } else if (_typeOperations.isNullType(type)) {
- return StaticType.nullType;
- } else if (_typeOperations.isNonNullableObject(type)) {
- return StaticType.nonNullableObject;
- } else if (_typeOperations.isNullableObject(type)) {
- return StaticType.nullableObject;
- }
-
- StaticType staticType;
- Type nonNullable = _typeOperations.getNonNullable(type);
- if (_typeOperations.isBoolType(nonNullable)) {
- staticType = _boolStaticType;
- } else if (_typeOperations.isRecordType(nonNullable)) {
- staticType = new RecordStaticType(_typeOperations, this, nonNullable);
- } else {
- EnumClass? enumClass = enumOperations.getEnumClass(nonNullable);
- if (enumClass != null) {
- staticType = new EnumStaticType(
- _typeOperations, this, nonNullable, _getEnumInfo(enumClass));
- } else {
- Class? sealedClass = _sealedClassOperations.getSealedClass(nonNullable);
- if (sealedClass != null) {
- staticType = new SealedClassStaticType(
- _typeOperations,
- this,
- nonNullable,
- this,
- _sealedClassOperations,
- _getSealedClassInfo(sealedClass));
- } else {
- staticType =
- new TypeBasedStaticType(_typeOperations, this, nonNullable);
- }
- }
- }
- if (_typeOperations.isNullable(type)) {
- staticType = staticType.nullable;
- }
- return staticType;
- }
-
- /// Returns the [StaticType] for the [enumElementValue] declared by
- /// [enumClass].
- StaticType getEnumElementStaticType(
- EnumClass enumClass, EnumElementValue enumElementValue) {
- return _getEnumInfo(enumClass).getEnumElement(enumElementValue);
- }
-
- /// Creates a new unique [StaticType].
- StaticType getUnknownStaticType() {
- return getUniqueStaticType(
- _typeOperations.nullableObjectType, new Object(), '?');
- }
-
- /// Returns a [StaticType] of the given [type] with the given
- /// [textualRepresentation] that unique identifies the [uniqueValue].
- ///
- /// This is used for constants that are neither bool nor enum values.
- StaticType getUniqueStaticType(
- Type type, Object uniqueValue, String textualRepresentation) {
- Type nonNullable = _typeOperations.getNonNullable(type);
- StaticType staticType = _uniqueTypeMap[uniqueValue] ??=
- new UniqueStaticType(_typeOperations, this, nonNullable, uniqueValue,
- textualRepresentation);
- if (_typeOperations.isNullable(type)) {
- staticType = staticType.nullable;
- }
- return staticType;
- }
-
- @override
- Map<String, StaticType> getFieldTypes(Type type) {
- Map<String, StaticType>? fields = _fieldCache[type];
- if (fields == null) {
- _fieldCache[type] = fields = {};
- for (MapEntry<String, Type> entry
- in _typeOperations.getFieldTypes(type).entries) {
- fields[entry.key] = getStaticType(entry.value);
- }
- }
- return fields;
- }
-}
-
-/// [EnumInfo] stores information to compute the static type for and the type
-/// of and enum class and its enum elements.
-class EnumInfo<Type extends Object, EnumClass extends Object,
- EnumElement extends Object, EnumElementValue extends Object> {
- final TypeOperations<Type> _typeOperations;
- final FieldLookup<Type> _fieldLookup;
- final EnumOperations<Type, EnumClass, EnumElement, EnumElementValue>
- _enumOperations;
- final EnumClass _enumClass;
- Map<EnumElementValue, EnumElementStaticType<Type, EnumElement>>?
- _enumElements;
-
- EnumInfo(this._typeOperations, this._fieldLookup, this._enumOperations,
- this._enumClass);
-
- /// Returns a map of the enum elements and their corresponding [StaticType]s
- /// declared by [_enumClass].
- Map<EnumElementValue, EnumElementStaticType<Type, EnumElement>>
- get enumElements => _enumElements ??= _createEnumElements();
-
- /// Returns the [StaticType] corresponding to [enumElementValue].
- EnumElementStaticType<Type, EnumElement> getEnumElement(
- EnumElementValue enumElementValue) {
- return enumElements[enumElementValue]!;
- }
-
- Map<EnumElementValue, EnumElementStaticType<Type, EnumElement>>
- _createEnumElements() {
- Map<EnumElementValue, EnumElementStaticType<Type, EnumElement>> elements =
- {};
- for (EnumElement element in _enumOperations.getEnumElements(_enumClass)) {
- EnumElementValue value = _enumOperations.getEnumElementValue(element);
- elements[value] = new EnumElementStaticType<Type, EnumElement>(
- _typeOperations,
- _fieldLookup,
- _enumOperations.getEnumElementType(element),
- element,
- _enumOperations.getEnumElementName(element));
- }
- return elements;
- }
-}
-
-/// [SealedClassInfo] stores information to compute the static type for a
-/// sealed class.
-class SealedClassInfo<Type extends Object, Class extends Object> {
- final SealedClassOperations<Type, Class> _sealedClassOperations;
- final Class _sealedClass;
- List<Class>? _subClasses;
-
- SealedClassInfo(this._sealedClassOperations, this._sealedClass);
-
- /// Returns the classes that directly extends, implements or mix in
- /// [_sealedClass].
- Iterable<Class> get subClasses =>
- _subClasses ??= _sealedClassOperations.getDirectSubclasses(_sealedClass);
-}
-
-/// [StaticType] based on a non-nullable [Type].
-///
-/// All [StaticType] implementation in this library are based on [Type] through
-/// this class. Additionally, the `static_type.dart` library has fixed
-/// [StaticType] implementations for `Object`, `Null`, `Never` and nullable
-/// types.
-class TypeBasedStaticType<Type extends Object> extends NonNullableStaticType {
- final TypeOperations<Type> _typeOperations;
- final FieldLookup<Type> _fieldLookup;
- final Type _type;
-
- TypeBasedStaticType(this._typeOperations, this._fieldLookup, this._type);
-
- @override
- Map<String, StaticType> get fields => _fieldLookup.getFieldTypes(_type);
-
- /// Returns a non-null value for static types that are unique subtypes of
- /// the [_type]. For instance individual elements of an enum.
- Object? get identity => null;
-
- @override
- bool isSubtypeOfInternal(StaticType other) {
- return other is TypeBasedStaticType<Type> &&
- (other.identity == null || identical(identity, other.identity)) &&
- _typeOperations.isSubtypeOf(_type, other._type);
- }
-
- @override
- bool get isSealed => false;
-
- @override
- String get name => _typeOperations.typeToString(_type);
-
- @override
- int get hashCode => Object.hash(_type, identity);
-
- @override
- bool operator ==(other) {
- if (identical(this, other)) return true;
- return other is TypeBasedStaticType<Type> &&
- _type == other._type &&
- identity == other.identity;
- }
-
- Type get typeForTesting => _type;
-}
-
-/// [StaticType] for an instantiation of an enum that support access to the
-/// enum values that populate its type through the [subtypes] property.
-class EnumStaticType<Type extends Object, EnumElement extends Object>
- extends TypeBasedStaticType<Type> {
- final EnumInfo<Type, Object, EnumElement, Object> _enumInfo;
- List<EnumElementStaticType<Type, EnumElement>>? _enumElements;
-
- EnumStaticType(
- super.typeOperations, super.fieldLookup, super.type, this._enumInfo);
-
- @override
- bool get isSealed => true;
-
- @override
- Iterable<StaticType> get subtypes => enumElements;
-
- List<EnumElementStaticType<Type, EnumElement>> get enumElements =>
- _enumElements ??= _createEnumElements();
-
- List<EnumElementStaticType<Type, EnumElement>> _createEnumElements() {
- List<EnumElementStaticType<Type, EnumElement>> elements = [];
- for (EnumElementStaticType<Type, EnumElement> enumElement
- in _enumInfo.enumElements.values) {
- if (_typeOperations.isSubtypeOf(enumElement._type, _type)) {
- elements.add(enumElement);
- }
- }
- return elements;
- }
-}
-
-/// [StaticType] for a single enum element.
-///
-/// In the [StaticType] model, individual enum elements are represented as
-/// unique subtypes of the enum type, modelled using [EnumStaticType].
-class EnumElementStaticType<Type extends Object, EnumElement extends Object>
- extends TypeBasedStaticType<Type> {
- final EnumElement enumElement;
-
- @override
- final String name;
-
- EnumElementStaticType(super.typeOperations, super.fieldLookup, super.type,
- this.enumElement, this.name);
-
- @override
- Object? get identity => enumElement;
-}
-
-/// [StaticType] for a sealed class type.
-class SealedClassStaticType<Type extends Object, Class extends Object>
- extends TypeBasedStaticType<Type> {
- final ExhaustivenessCache<Type, dynamic, dynamic, dynamic, Class> _cache;
- final SealedClassOperations<Type, Class> _sealedClassOperations;
- final SealedClassInfo<Type, Class> _sealedInfo;
- Iterable<StaticType>? _subtypes;
-
- SealedClassStaticType(super.typeOperations, super.fieldLookup, super.type,
- this._cache, this._sealedClassOperations, this._sealedInfo);
-
- @override
- bool get isSealed => true;
-
- @override
- Iterable<StaticType> get subtypes => _subtypes ??= _createSubtypes();
-
- List<StaticType> _createSubtypes() {
- List<StaticType> subtypes = [];
- for (Class subClass in _sealedInfo.subClasses) {
- Type? subtype =
- _sealedClassOperations.getSubclassAsInstanceOf(subClass, _type);
- if (subtype != null) {
- assert(_typeOperations.isSubtypeOf(subtype, _type));
- subtypes.add(_cache.getStaticType(subtype));
- }
- }
- return subtypes;
- }
-}
-
-/// [StaticType] for an object uniquely defined by its [identity].
-class UniqueStaticType<Type extends Object> extends TypeBasedStaticType<Type> {
- @override
- final Object identity;
-
- @override
- final String name;
-
- UniqueStaticType(super.typeOperations, super.fieldLookup, super.type,
- this.identity, this.name);
-}
-
-/// [StaticType] for the `bool` type.
-class BoolStaticType<Type extends Object> extends TypeBasedStaticType<Type> {
- BoolStaticType(super.typeOperations, super.fieldLookup, super.type);
-
- @override
- bool get isSealed => true;
-
- late StaticType trueType =
- new UniqueStaticType(_typeOperations, _fieldLookup, _type, true, 'true');
-
- late StaticType falseType = new UniqueStaticType(
- _typeOperations, _fieldLookup, _type, false, 'false');
-
- @override
- Iterable<StaticType> get subtypes => [trueType, falseType];
-}
-
-/// [StaticType] for a record type.
-///
-/// This models that type aspect of the record using only the structure of the
-/// record type. This means that the type for `(Object, String)` and
-/// `(String, int)` will be subtypes of each other.
-///
-/// This is necessary to avoid invalid conclusions on the disjointness of
-/// spaces base on the their types. For instance in
-///
-/// method((String, Object) o) {
-/// if (o case (Object _, String s)) {}
-/// }
-///
-/// the case is not empty even though `(String, Object)` and `(Object, String)`
-/// are not related type-wise.
-///
-/// Not that the fields of the record types _are_ using the type, so that
-/// the `$1` field of `(String, Object)` is known to contain only `String`s.
-class RecordStaticType<Type extends Object> extends TypeBasedStaticType<Type> {
- RecordStaticType(super.typeOperations, super.fieldLookup, super.type);
-
- @override
- bool get isRecord => true;
-
- @override
- bool isSubtypeOfInternal(StaticType other) {
- if (other is! RecordStaticType<Type>) {
- return false;
- }
- assert(identity == null);
- if (fields.length != other.fields.length) {
- return false;
- }
- for (MapEntry<String, StaticType> field in fields.entries) {
- StaticType? type = other.fields[field.key];
- if (type == null) {
- return false;
- }
- }
- return true;
- }
-}
diff --git a/_fe_analyzer_shared/lib/src/exhaustiveness/subtract.dart b/_fe_analyzer_shared/lib/src/exhaustiveness/subtract.dart
deleted file mode 100644
index 97706e1..0000000
--- a/_fe_analyzer_shared/lib/src/exhaustiveness/subtract.dart
+++ /dev/null
@@ -1,187 +0,0 @@
-// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-import 'intersect_empty.dart';
-import 'profile.dart' as profile;
-import 'space.dart';
-import 'static_type.dart';
-
-/// Recursively replaces [left] with a union of its sealed subtypes as long as
-/// doing so enables it to more precisely match against [right].
-List<StaticType> expandType(StaticType left, StaticType right) {
- // If [left] is nullable and right is null or non-nullable, then expand the
- // nullable type.
- if (left is NullableStaticType &&
- (right == StaticType.nullType || right is! NullableStaticType)) {
- return [...expandType(left.underlying, right), StaticType.nullType];
- }
-
- // If [right] is nullable, then expand using its underlying type.
- if (right is NullableStaticType) {
- return expandType(left, right.underlying);
- }
-
- // If [left] is a sealed supertype and [right] is in its subtype hierarchy,
- // then expand out the subtypes (recursively) to more precisely match [right].
- if (left.isSealed && left != right && right.isSubtypeOf(left)) {
- return {
- for (StaticType subtype in left.subtypes) ...expandType(subtype, right),
- }.toList();
- }
-
- if (left == StaticType.neverType) {
- return const [];
- }
- return [left];
-}
-
-/// Returns a new [Space] that contains all of the values of [left] that are
-/// not also in [right].
-Space subtract(Space left, Space right) {
- profile.count('subtract');
-
- // Subtracting from empty is still empty.
- if (left == Space.empty) return Space.empty;
-
- // Subtracting nothing leaves it unchanged.
- if (right == Space.empty) return left;
-
- // Distribute a union on the left.
- // A|B - x => A-x | B-x
- if (left is UnionSpace) {
- return new Space.union(
- left.arms.map((arm) => subtract(arm, right)).toList());
- }
-
- // Distribute a union on the right.
- // x - A|B => x - A - B
- if (right is UnionSpace) {
- Space result = left;
- for (Space arm in right.arms) {
- result = subtract(result, arm);
- }
- return result;
- }
-
- // Otherwise, it must be two extract spaces.
- return _subtractExtract(left as ExtractSpace, right as ExtractSpace);
-}
-
-/// Returns `true` if every field in [leftFields] is covered by the
-/// corresponding field in [rightFields].
-bool _isLeftSubspace(StaticType leftType, List<String> fieldNames,
- Map<String, Space> leftFields, Map<String, Space> rightFields) {
- for (String name in fieldNames) {
- if (subtract(leftFields[name]!, rightFields[name]!) != Space.empty) {
- return false;
- }
- }
-
- // If we get here, every field covered.
- return true;
-}
-
-/// Subtract [right] from [left].
-Space _subtractExtract(ExtractSpace left, ExtractSpace right) {
- List<String> fieldNames =
- {...left.fields.keys, ...right.fields.keys}.toList();
-
- List<Space> spaces = <Space>[];
-
- // If the left type is in a sealed hierarchy, expanding it to its subtypes
- // might let us calculate the subtraction more precisely.
- List<StaticType> subtypes = expandType(left.type, right.type);
- for (StaticType subtype in subtypes) {
- spaces.addAll(_subtractExtractAtType(subtype, left, right, fieldNames));
- }
-
- return new Space.union(spaces);
-}
-
-/// Subtract [right] from [left], but using [type] for left's type, which may
-/// be a more specific subtype of [left]'s own type is a sealed supertype.
-List<Space> _subtractExtractAtType(StaticType type, ExtractSpace left,
- ExtractSpace right, List<String> fieldNames) {
- // If the right type doesn't cover the left (even after expanding sealed
- // types), then we can't do anything with the fields since they may not
- // even come into play for all values. Subtract nothing from this subtype
- // and keep all of the current fields.
- if (!type.isSubtypeOf(right.type)) return [new Space(type, left.fields)];
-
- // Infer any fields that appear in one space and not the other.
- Map<String, Space> leftFields = <String, Space>{};
- Map<String, Space> rightFields = <String, Space>{};
- for (String name in fieldNames) {
- // If the right space matches on a field that the left doesn't have, infer
- // it from the static type of the field. That contains the same set of
- // values as having no field at all.
- assert(type.fields.containsKey(name), "Field '$name' not found in $type.");
- leftFields[name] = left.fields[name] ?? new Space(type.fields[name]!);
-
- // If the left matches on a field that the right doesn't have, infer top
- // for the right field since the right will accept any of left's values for
- // that field.
- rightFields[name] = right.fields[name] ?? Space.top;
- }
-
- // If any pair of fields have no overlapping values, then no overall value
- // that matches the left space will also match the right space. So the right
- // space doesn't subtract anything and we keep the left space as-is.
- for (String name in fieldNames) {
- if (spacesHaveEmptyIntersection(leftFields[name]!, rightFields[name]!)) {
- return [new Space(type, left.fields)];
- }
- }
-
- // If all the right's fields strictly cover all of the left's, then the
- // right completely subtracts this type and nothing remains.
- if (_isLeftSubspace(type, fieldNames, leftFields, rightFields)) {
- return const [];
- }
-
- // The right side is a supertype but its fields don't totally cover, so
- // handle each of them individually.
-
- // Walk the fields and see which ones are modified by the right-hand fields.
- Map<String, Space> fixed = <String, Space>{};
- Map<String, Space> changedDifference = <String, Space>{};
- for (String name in fieldNames) {
- Space difference = subtract(leftFields[name]!, rightFields[name]!);
- if (difference == Space.empty) {
- // The right field accepts all the values that the left field accepts, so
- // keep the left field as it is.
- fixed[name] = leftFields[name]!;
- } else if (difference.isTop) {
- // If the resulting field matches everything, simply discard it since
- // it's equivalent to omitting the field.
- } else {
- changedDifference[name] = difference;
- }
- }
-
- // If no fields are affected by the subtraction, just return a single arm
- // with all of the fields.
- if (changedDifference.isEmpty) return [new Space(type, fixed)];
-
- // For each field whose `left - right` is different, include an arm that
- // includes that one difference.
- List<String> changedFields = changedDifference.keys.toList();
- List<Space> spaces = <Space>[];
- for (int i = 0; i < changedFields.length; i++) {
- Map<String, Space> fields = {...fixed};
-
- for (int j = 0; j < changedFields.length; j++) {
- String name = changedFields[j];
- if (i == j) {
- fields[name] = changedDifference[name]!;
- } else {
- fields[name] = leftFields[name]!;
- }
- }
-
- spaces.add(new Space(type, fields));
- }
-
- return spaces;
-}
diff --git a/_fe_analyzer_shared/lib/src/exhaustiveness/test_helper.dart b/_fe_analyzer_shared/lib/src/exhaustiveness/test_helper.dart
index 2bf6915..9e3bdb8 100644
--- a/_fe_analyzer_shared/lib/src/exhaustiveness/test_helper.dart
+++ b/_fe_analyzer_shared/lib/src/exhaustiveness/test_helper.dart
@@ -2,8 +2,8 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-import 'package:_fe_analyzer_shared/src/exhaustiveness/exhaustive.dart';
-
+import 'exhaustive.dart';
+import 'key.dart';
import 'space.dart';
import 'static_type.dart';
@@ -14,30 +14,36 @@
static const String scrutineeFields = 'fields';
static const String space = 'space';
static const String subtypes = 'subtypes';
- static const String remaining = 'remaining';
+ static const String expandedSubtypes = 'expandedSubtypes';
}
/// Returns a textual representation for [space] used for testing.
-String spaceToText(Space space) => space.toString();
+String spacesToText(Space space) {
+ String text = space.toString();
+ if (text.startsWith('[') && text.endsWith(']')) {
+ // Avoid list-like syntax which collides with the [Features] encoding.
+ return '<$text>';
+ }
+ return text;
+}
/// Returns a textual representation for [fields] used for testing.
-String fieldsToText(Map<String, StaticType> fields) {
- // TODO(johnniwinther): Enforce that field maps are always sorted.
- List<String> sortedNames = fields.keys.toList()..sort();
+String fieldsToText(StaticType type, ObjectFieldLookup objectFieldLookup,
+ Set<Key> fieldsOfInterest) {
+ List<Key> sortedNames = fieldsOfInterest.toList()..sort();
StringBuffer sb = new StringBuffer();
String comma = '';
sb.write('{');
- for (String name in sortedNames) {
- if (name.startsWith('_')) {
- // Avoid implementation specific fields, like `Object._identityHashCode`
- // and `Enum._name`.
- // TODO(johnniwinther): Support private fields in the test code.
- continue;
- }
+ for (Key key in sortedNames) {
+ StaticType? fieldType = type.getField(objectFieldLookup, key);
sb.write(comma);
- sb.write(name);
+ sb.write(key.name);
sb.write(':');
- sb.write(staticTypeToText(fields[name]!));
+ if (fieldType != null) {
+ sb.write(staticTypeToText(fieldType));
+ } else {
+ sb.write("-");
+ }
comma = ',';
}
sb.write('}');
@@ -48,14 +54,13 @@
String staticTypeToText(StaticType type) => type.toString();
/// Returns a textual representation of the subtypes of [type] used for testing.
-String? subtypesToText(StaticType type) {
- List<StaticType> subtypes = type.subtypes.toList();
- if (subtypes.isEmpty) return null;
- // TODO(johnniwinther): Sort subtypes.
+String? typesToText(Iterable<StaticType> types) {
+ if (types.isEmpty) return null;
+ // TODO(johnniwinther): Sort types.
StringBuffer sb = new StringBuffer();
String comma = '';
sb.write('{');
- for (StaticType subtype in subtypes) {
+ for (StaticType subtype in types) {
sb.write(comma);
sb.write(staticTypeToText(subtype));
comma = ',';
diff --git a/_fe_analyzer_shared/lib/src/exhaustiveness/types.dart b/_fe_analyzer_shared/lib/src/exhaustiveness/types.dart
new file mode 100644
index 0000000..a08f61a
--- /dev/null
+++ b/_fe_analyzer_shared/lib/src/exhaustiveness/types.dart
@@ -0,0 +1,187 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'key.dart';
+import 'shared.dart';
+import 'space.dart';
+import 'static_type.dart';
+import 'witness.dart';
+
+part 'types/bool.dart';
+part 'types/enum.dart';
+part 'types/future_or.dart';
+part 'types/list.dart';
+part 'types/map.dart';
+part 'types/record.dart';
+part 'types/sealed.dart';
+
+/// [StaticType] based on a non-nullable [Type].
+///
+/// All [StaticType] implementation in this library are based on [Type] through
+/// this class. Additionally, the `static_type.dart` library has fixed
+/// [StaticType] implementations for `Object`, `Null`, `Never` and nullable
+/// types.
+class TypeBasedStaticType<Type extends Object> extends NonNullableStaticType {
+ final TypeOperations<Type> _typeOperations;
+ final FieldLookup<Type> _fieldLookup;
+ final Type _type;
+
+ TypeBasedStaticType(this._typeOperations, this._fieldLookup, this._type);
+
+ @override
+ Map<Key, StaticType> get fields => _fieldLookup.getFieldTypes(_type);
+
+ @override
+ StaticType? getAdditionalField(Key key) =>
+ _fieldLookup.getAdditionalFieldType(_type, key);
+
+ /// Returns a [Restriction] value for static types the determines subtypes of
+ /// the [_type]. For instance individual elements of an enum.
+ Restriction get restriction => const Unrestricted();
+
+ @override
+ bool isSubtypeOfInternal(StaticType other) {
+ return other is TypeBasedStaticType<Type> &&
+ _typeOperations.isSubtypeOf(_type, other._type) &&
+ restriction.isSubtypeOf(_typeOperations, other.restriction);
+ }
+
+ @override
+ bool get isSealed => false;
+
+ @override
+ String get name => _typeOperations.typeToString(_type);
+
+ @override
+ int get hashCode => Object.hash(_type, restriction);
+
+ @override
+ bool operator ==(other) {
+ if (identical(this, other)) return true;
+ return other is TypeBasedStaticType<Type> &&
+ _type == other._type &&
+ restriction == other.restriction;
+ }
+
+ Type get typeForTesting => _type;
+
+ @override
+ void witnessToText(StringBuffer buffer, FieldWitness witness,
+ Map<Key, FieldWitness> witnessFields) {
+ if (!_typeOperations.hasSimpleName(_type)) {
+ buffer.write(name);
+ buffer.write(' _');
+
+ // If we have restrictions on the record type we create an and pattern.
+ String additionalStart = ' && Object(';
+ String additionalEnd = '';
+ String comma = '';
+ for (MapEntry<Key, FieldWitness> entry in witnessFields.entries) {
+ Key key = entry.key;
+ if (key is! ListKey) {
+ buffer.write(additionalStart);
+ additionalStart = '';
+ additionalEnd = ')';
+ buffer.write(comma);
+ comma = ', ';
+
+ buffer.write(key.name);
+ buffer.write(': ');
+ FieldWitness field = entry.value;
+ field.witnessToText(buffer);
+ }
+ }
+ buffer.write(additionalEnd);
+ } else {
+ super.witnessToText(buffer, witness, witnessFields);
+ }
+ }
+}
+
+/// [StaticType] for an object restricted by its [restriction].
+abstract class RestrictedStaticType<Type extends Object,
+ Identity extends Restriction> extends TypeBasedStaticType<Type> {
+ @override
+ final Identity restriction;
+
+ @override
+ final String name;
+
+ RestrictedStaticType(super.typeOperations, super.fieldLookup, super.type,
+ this.restriction, this.name);
+}
+
+/// [StaticType] for an object restricted to a single value.
+class ValueStaticType<Type extends Object, T extends Object>
+ extends RestrictedStaticType<Type, IdentityRestriction<T>> {
+ ValueStaticType(super.typeOperations, super.fieldLookup, super.type,
+ super.restriction, super.name);
+
+ @override
+ void witnessToText(StringBuffer buffer, FieldWitness witness,
+ Map<Key, FieldWitness> witnessFields) {
+ buffer.write(name);
+
+ // If we have restrictions on the value we create an and pattern.
+ String additionalStart = ' && Object(';
+ String additionalEnd = '';
+ String comma = '';
+ for (MapEntry<Key, FieldWitness> entry in witnessFields.entries) {
+ Key key = entry.key;
+ if (key is! RecordKey) {
+ buffer.write(additionalStart);
+ additionalStart = '';
+ additionalEnd = ')';
+ buffer.write(comma);
+ comma = ', ';
+
+ buffer.write(key.name);
+ buffer.write(': ');
+ FieldWitness field = entry.value;
+ field.witnessToText(buffer);
+ }
+ }
+ buffer.write(additionalEnd);
+ }
+}
+
+/// Interface for a restriction within a subtype relation.
+///
+/// This is used for instance to model enum values within an enum type and
+/// map patterns within a map type.
+abstract class Restriction<Type extends Object> {
+ /// Returns `true` if this [Restriction] covers the whole type.
+ bool get isUnrestricted;
+
+ /// Returns `true` if this restriction is a subtype of [other].
+ bool isSubtypeOf(TypeOperations<Type> typeOperations, Restriction other);
+}
+
+/// The unrestricted [Restriction] that covers all values of a type.
+class Unrestricted implements Restriction<Object> {
+ const Unrestricted();
+
+ @override
+ bool get isUnrestricted => true;
+
+ @override
+ bool isSubtypeOf(TypeOperations<Object> typeOperations, Restriction other) =>
+ other.isUnrestricted;
+}
+
+/// [Restriction] based a unique [identity] value.
+class IdentityRestriction<Identity extends Object>
+ implements Restriction<Object> {
+ final Identity identity;
+
+ const IdentityRestriction(this.identity);
+
+ @override
+ bool get isUnrestricted => false;
+
+ @override
+ bool isSubtypeOf(TypeOperations<Object> typeOperations, Restriction other) =>
+ other.isUnrestricted ||
+ other is IdentityRestriction<Identity> && identity == other.identity;
+}
diff --git a/_fe_analyzer_shared/lib/src/exhaustiveness/types/bool.dart b/_fe_analyzer_shared/lib/src/exhaustiveness/types/bool.dart
new file mode 100644
index 0000000..cfcf23b
--- /dev/null
+++ b/_fe_analyzer_shared/lib/src/exhaustiveness/types/bool.dart
@@ -0,0 +1,23 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+part of '../types.dart';
+
+/// [StaticType] for the `bool` type.
+class BoolStaticType<Type extends Object> extends TypeBasedStaticType<Type> {
+ BoolStaticType(super.typeOperations, super.fieldLookup, super.type);
+
+ @override
+ bool get isSealed => true;
+
+ late StaticType trueType = new ValueStaticType<Type, bool>(_typeOperations,
+ _fieldLookup, _type, const IdentityRestriction<bool>(true), 'true');
+
+ late StaticType falseType = new ValueStaticType<Type, bool>(_typeOperations,
+ _fieldLookup, _type, const IdentityRestriction<bool>(false), 'false');
+
+ @override
+ Iterable<StaticType> getSubtypes(Set<Key> keysOfInterest) =>
+ [trueType, falseType];
+}
diff --git a/_fe_analyzer_shared/lib/src/exhaustiveness/types/enum.dart b/_fe_analyzer_shared/lib/src/exhaustiveness/types/enum.dart
new file mode 100644
index 0000000..548aafd
--- /dev/null
+++ b/_fe_analyzer_shared/lib/src/exhaustiveness/types/enum.dart
@@ -0,0 +1,138 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+part of '../types.dart';
+
+/// Interface implemented by analyzer/CFE to support [StaticType]s for enums.
+abstract class EnumOperations<Type extends Object, EnumClass extends Object,
+ EnumElement extends Object, EnumElementValue extends Object> {
+ /// Returns the enum class declaration for the [type] or `null` if
+ /// [type] is not an enum type.
+ EnumClass? getEnumClass(Type type);
+
+ /// Returns the enum elements defined by [enumClass].
+ Iterable<EnumElement> getEnumElements(EnumClass enumClass);
+
+ /// Returns the value defined by the [enumElement]. The encoding is specific
+ /// the implementation of this interface but must ensure constant value
+ /// identity.
+ EnumElementValue getEnumElementValue(EnumElement enumElement);
+
+ /// Returns the declared name of the [enumElement].
+ String getEnumElementName(EnumElement enumElement);
+
+ /// Returns the static type of the [enumElement].
+ Type getEnumElementType(EnumElement enumElement);
+}
+
+/// [EnumInfo] stores information to compute the static type for and the type
+/// of and enum class and its enum elements.
+class EnumInfo<Type extends Object, EnumClass extends Object,
+ EnumElement extends Object, EnumElementValue extends Object> {
+ final TypeOperations<Type> _typeOperations;
+ final FieldLookup<Type> _fieldLookup;
+ final EnumOperations<Type, EnumClass, EnumElement, EnumElementValue>
+ _enumOperations;
+ final EnumClass _enumClass;
+ Map<EnumElementValue, EnumElementStaticType<Type, EnumElement>>?
+ _enumElements;
+
+ EnumInfo(this._typeOperations, this._fieldLookup, this._enumOperations,
+ this._enumClass);
+
+ /// Returns a map of the enum elements and their corresponding [StaticType]s
+ /// declared by [_enumClass].
+ Map<EnumElementValue, EnumElementStaticType<Type, EnumElement>>
+ get enumElements => _enumElements ??= _createEnumElements();
+
+ /// Returns the [StaticType] corresponding to [enumElementValue].
+ EnumElementStaticType<Type, EnumElement> getEnumElement(
+ EnumElementValue enumElementValue) {
+ return enumElements[enumElementValue]!;
+ }
+
+ Map<EnumElementValue, EnumElementStaticType<Type, EnumElement>>
+ _createEnumElements() {
+ Map<EnumElementValue, EnumElementStaticType<Type, EnumElement>> elements =
+ {};
+ for (EnumElement element in _enumOperations.getEnumElements(_enumClass)) {
+ EnumElementValue value = _enumOperations.getEnumElementValue(element);
+ elements[value] = new EnumElementStaticType<Type, EnumElement>(
+ _typeOperations,
+ _fieldLookup,
+ _enumOperations.getEnumElementType(element),
+ new IdentityRestriction<EnumElement>(element),
+ _enumOperations.getEnumElementName(element));
+ }
+ return elements;
+ }
+}
+
+/// [StaticType] for an instantiation of an enum that support access to the
+/// enum values that populate its type through the [subtypes] property.
+class EnumStaticType<Type extends Object, EnumElement extends Object>
+ extends TypeBasedStaticType<Type> {
+ final EnumInfo<Type, Object, EnumElement, Object> _enumInfo;
+ List<StaticType>? _enumElements;
+
+ EnumStaticType(
+ super.typeOperations, super.fieldLookup, super.type, this._enumInfo);
+
+ @override
+ bool get isSealed => true;
+
+ @override
+ Iterable<StaticType> getSubtypes(Set<Key> keysOfInterest) => enumElements;
+
+ List<StaticType> get enumElements => _enumElements ??= _createEnumElements();
+
+ List<StaticType> _createEnumElements() {
+ List<StaticType> elements = [];
+ for (EnumElementStaticType<Type, EnumElement> enumElement
+ in _enumInfo.enumElements.values) {
+ // For generic enums, the individual enum elements might not be subtypes
+ // of the concrete enum type. For instance
+ //
+ // enum E<T> {
+ // a<int>(),
+ // b<String>(),
+ // c<bool>(),
+ // }
+ //
+ // method<T extends num>(E<T> e) {
+ // switch (e) { ... }
+ // }
+ //
+ // Here the enum elements `E.b` and `E.c` cannot be actual values of `e`
+ // because of the bound `num` on `T`.
+ //
+ // We detect this by checking whether the enum element type is a subtype
+ // of the overapproximation of [_type], in this case whether the element
+ // types are subtypes of `E<num>`.
+ //
+ // Since all type arguments on enum values are fixed, we don't have to
+ // avoid the trivial subtype instantiation `E<Never>`.
+ if (_typeOperations.isSubtypeOf(
+ enumElement._type, _typeOperations.overapproximate(_type))) {
+ // Since the type of the enum element might not itself be a subtype of
+ // [_type], for instance in the example above the type of `Enum.a`,
+ // `Enum<int>`, is not a subtype of `Enum<T>`, we wrap the static type
+ // to establish the subtype relation between the [StaticType] for the
+ // enum element and this [StaticType].
+ elements.add(new WrappedStaticType(enumElement, this));
+ }
+ }
+ return elements;
+ }
+}
+
+/// [StaticType] for a single enum element.
+///
+/// In the [StaticType] model, individual enum elements are represented as
+/// unique subtypes of the enum type, modelled using [EnumStaticType].
+class EnumElementStaticType<Type extends Object, EnumElement extends Object>
+ extends ValueStaticType<Type, EnumElement> {
+ EnumElementStaticType(super.typeOperations, super.fieldLookup, super.type,
+ super.restriction, super.name);
+}
diff --git a/_fe_analyzer_shared/lib/src/exhaustiveness/types/future_or.dart b/_fe_analyzer_shared/lib/src/exhaustiveness/types/future_or.dart
new file mode 100644
index 0000000..5ae8dc2
--- /dev/null
+++ b/_fe_analyzer_shared/lib/src/exhaustiveness/types/future_or.dart
@@ -0,0 +1,27 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+part of '../types.dart';
+
+/// [StaticType] for a `FutureOr<T>` type for some type `T`.
+///
+/// This is a sealed type where the subtypes for are `T` and `Future<T>`.
+class FutureOrStaticType<Type extends Object>
+ extends TypeBasedStaticType<Type> {
+ /// The type for `T`.
+ final StaticType _typeArgument;
+
+ /// The type for `Future<T>`.
+ final StaticType _futureType;
+
+ FutureOrStaticType(super.typeOperations, super.fieldLookup, super.type,
+ this._typeArgument, this._futureType);
+
+ @override
+ bool get isSealed => true;
+
+ @override
+ Iterable<StaticType> getSubtypes(Set<Key> keysOfInterest) =>
+ [_typeArgument, _futureType];
+}
diff --git a/_fe_analyzer_shared/lib/src/exhaustiveness/types/list.dart b/_fe_analyzer_shared/lib/src/exhaustiveness/types/list.dart
new file mode 100644
index 0000000..ec23b8a
--- /dev/null
+++ b/_fe_analyzer_shared/lib/src/exhaustiveness/types/list.dart
@@ -0,0 +1,277 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+part of '../types.dart';
+
+/// [StaticType] for a list type which can be divided into subtypes of
+/// [ListPatternStaticType].
+///
+/// This is used to support exhaustiveness checking for list types by
+/// contextually dividing the list into relevant cases for checking.
+///
+/// For instance, the exhaustiveness can be achieved by a single pattern
+///
+/// case [...]:
+///
+/// or by two disjoint patterns:
+///
+/// case []:
+/// case [_, ...]:
+///
+/// When checking for exhaustiveness, witness candidates are created and tested
+/// against the available cases. This means that the chosen candidates must be
+/// matched by at least one case or the candidate is considered a witness of
+/// non-exhaustiveness.
+///
+/// Looking at the first example, we could choose `[...]`, the list of
+/// arbitrary size, as a candidate. This works for the first example, since the
+/// case `[...]` matches the list of arbitrary size. But if we tried to use this
+/// on the second example it would fail, since neither `[]` nor `[_, ...]` fully
+/// matches the list of arbitrary size.
+///
+/// A solution could be to choose candidates `[]` and `[_, ...]`, the empty list
+/// and the list of 1 or more elements. This would work for the first example,
+/// since `[...]` matches both the empty list and the list of 1 or more
+/// elements. It also works for the second example, since `[]` matches the empty
+/// list and `[_, ...]` matches the list of 1 or more elements.
+///
+/// But now comes a third way of exhaustively matching a list:
+///
+/// case []:
+/// case [_]:
+/// case [_, _, ...]:
+///
+/// and our candidates no longer work, since while `[]` does match the empty
+/// list, neither `[_]` nor `[_, _, ...]` matches the list of 1 or more
+/// elements.
+///
+/// This shows us that there can be no fixed set of witness candidates that we
+/// can use to match a list type.
+///
+/// What we do instead, is to create the set of witness candidates based on the
+/// cases that should match it. We find the maximal number, n, of fixed, i.e.
+/// non-rest, elements in the cases, and then create the lists of sizes 0 to n-1
+/// and the list of n or more elements as the witness candidates.
+class ListTypeStaticType<Type extends Object>
+ extends TypeBasedStaticType<Type> {
+ ListTypeStaticType(super.typeOperations, super.fieldLookup, super.type);
+
+ @override
+ bool get isSealed => true;
+
+ @override
+ Iterable<StaticType> getSubtypes(Set<Key> keysOfInterest) {
+ int maxHeadSize = 0;
+ int maxTailSize = 0;
+ for (Key key in keysOfInterest) {
+ if (key is HeadKey) {
+ if (key.index >= maxHeadSize) {
+ maxHeadSize = key.index + 1;
+ }
+ } else if (key is TailKey) {
+ if (key.index >= maxTailSize) {
+ maxTailSize = key.index + 1;
+ }
+ }
+ }
+ int maxSize = maxHeadSize + maxTailSize;
+ List<StaticType> subtypes = [];
+ Type elementType = _typeOperations.getListElementType(_type)!;
+ String typeArgumentText;
+ if (_typeOperations.isDynamic(elementType)) {
+ typeArgumentText = '';
+ } else {
+ typeArgumentText = '<${_typeOperations.typeToString(elementType)}>';
+ }
+ for (int size = 0; size < maxSize; size++) {
+ ListTypeIdentity<Type> identity = new ListTypeIdentity(
+ elementType, typeArgumentText,
+ size: size, hasRest: false);
+ subtypes.add(new ListPatternStaticType<Type>(
+ _typeOperations, _fieldLookup, _type, identity, identity.toString()));
+ }
+ ListTypeIdentity<Type> identity = new ListTypeIdentity(
+ elementType, typeArgumentText,
+ size: maxSize, hasRest: true);
+ subtypes.add(new ListPatternStaticType<Type>(
+ _typeOperations, _fieldLookup, _type, identity, identity.toString()));
+ return subtypes;
+ }
+}
+
+/// [StaticType] for a list pattern type using a [ListTypeIdentity] for its
+/// uniqueness.
+class ListPatternStaticType<Type extends Object>
+ extends RestrictedStaticType<Type, ListTypeIdentity<Type>> {
+ ListPatternStaticType(super.typeOperations, super.fieldLookup, super.type,
+ super.restriction, super.name);
+
+ @override
+ String spaceToText(
+ Map<Key, Space> spaceFields, Map<Key, Space> additionalSpaceFields) {
+ StringBuffer buffer = new StringBuffer();
+ buffer.write(restriction.typeArgumentText);
+ buffer.write('[');
+
+ bool first = true;
+ additionalSpaceFields.forEach((Key key, Space space) {
+ if (!first) buffer.write(', ');
+ if (key is RestKey) {
+ buffer.write('...');
+ }
+ buffer.write(space);
+ first = false;
+ });
+
+ buffer.write(']');
+ return buffer.toString();
+ }
+
+ @override
+ void witnessToText(StringBuffer buffer, FieldWitness witness,
+ Map<Key, FieldWitness> witnessFields) {
+ int maxHeadSize = 0;
+ int maxTailSize = 0;
+ FieldWitness? restWitness;
+ for (MapEntry<Key, FieldWitness> entry in witnessFields.entries) {
+ Key key = entry.key;
+ if (key is HeadKey && key.index >= maxHeadSize) {
+ maxHeadSize = key.index + 1;
+ } else if (key is TailKey && key.index >= maxHeadSize) {
+ maxTailSize = key.index + 1;
+ } else if (key is RestKey) {
+ // TODO(johnniwinther): Can the rest key have head/tail sizes that don't
+ // match the found max head/tail sizes?
+ restWitness = entry.value;
+ }
+ }
+ if (maxHeadSize + maxTailSize < restriction.size) {
+ maxHeadSize = restriction.size - maxTailSize;
+ }
+ buffer.write('[');
+ String comma = '';
+ for (int index = 0; index < maxHeadSize; index++) {
+ buffer.write(comma);
+ Key key = new HeadKey(index);
+ FieldWitness? witness = witnessFields[key];
+ if (witness != null) {
+ witness.witnessToText(buffer);
+ } else {
+ buffer.write('_');
+ }
+ comma = ', ';
+ }
+ if (restriction.hasRest) {
+ buffer.write(comma);
+ buffer.write('...');
+ if (restWitness != null) {
+ restWitness.witnessToText(buffer);
+ }
+ comma = ', ';
+ }
+ for (int index = maxTailSize - 1; index >= 0; index--) {
+ buffer.write(comma);
+ Key key = new TailKey(index);
+ FieldWitness? witness = witnessFields[key];
+ if (witness != null) {
+ witness.witnessToText(buffer);
+ } else {
+ buffer.write('_');
+ }
+ comma = ', ';
+ }
+ buffer.write(']');
+
+ // If we have restrictions on the record type we create an and pattern.
+ String additionalStart = ' && Object(';
+ String additionalEnd = '';
+ comma = '';
+ for (MapEntry<Key, FieldWitness> entry in witnessFields.entries) {
+ Key key = entry.key;
+ if (key is! ListKey) {
+ buffer.write(additionalStart);
+ additionalStart = '';
+ additionalEnd = ')';
+ buffer.write(comma);
+ comma = ', ';
+
+ buffer.write(key.name);
+ buffer.write(': ');
+ FieldWitness field = entry.value;
+ field.witnessToText(buffer);
+ }
+ }
+ buffer.write(additionalEnd);
+ }
+}
+
+/// Identity object used for creating a unique [ListPatternStaticType] for a
+/// list pattern.
+///
+/// The uniqueness is defined by the element type, the number of elements at the
+/// start of the list, whether the list pattern has a rest element, and the
+/// number elements at the end of the list, after the rest element.
+class ListTypeIdentity<Type extends Object> implements Restriction<Type> {
+ final Type elementType;
+ final int size;
+ final bool hasRest;
+ final String typeArgumentText;
+
+ ListTypeIdentity(this.elementType, this.typeArgumentText,
+ {required this.size, required this.hasRest});
+
+ @override
+ late final int hashCode = Object.hash(elementType, size, hasRest);
+
+ @override
+ bool get isUnrestricted {
+ // The map pattern containing only a rest pattern covers the whole type.
+ return hasRest && size == 0;
+ }
+
+ @override
+ bool operator ==(Object other) {
+ if (identical(this, other)) return true;
+ return other is ListTypeIdentity<Type> &&
+ elementType == other.elementType &&
+ size == other.size &&
+ hasRest == other.hasRest;
+ }
+
+ @override
+ bool isSubtypeOf(TypeOperations<Type> typeOperations, Restriction other) {
+ if (other.isUnrestricted) return true;
+ if (other is! ListTypeIdentity<Type>) return false;
+ if (!typeOperations.isSubtypeOf(elementType, other.elementType)) {
+ return false;
+ }
+ if (other.hasRest) {
+ return size >= other.size;
+ } else if (hasRest) {
+ return false;
+ } else {
+ return size == other.size;
+ }
+ }
+
+ @override
+ String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.write(typeArgumentText);
+ sb.write('[');
+ String comma = '';
+ for (int i = 0; i < size; i++) {
+ sb.write(comma);
+ sb.write('()');
+ comma = ', ';
+ }
+ if (hasRest) {
+ sb.write(comma);
+ sb.write('...');
+ comma = ', ';
+ }
+ sb.write(']');
+ return sb.toString();
+ }
+}
diff --git a/_fe_analyzer_shared/lib/src/exhaustiveness/types/map.dart b/_fe_analyzer_shared/lib/src/exhaustiveness/types/map.dart
new file mode 100644
index 0000000..e15db86
--- /dev/null
+++ b/_fe_analyzer_shared/lib/src/exhaustiveness/types/map.dart
@@ -0,0 +1,159 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+part of '../types.dart';
+
+/// [StaticType] for a map pattern type using a [MapTypeIdentity] for its
+/// uniqueness.
+class MapPatternStaticType<Type extends Object>
+ extends RestrictedStaticType<Type, MapTypeIdentity<Type>> {
+ MapPatternStaticType(super.typeOperations, super.fieldLookup, super.type,
+ super.restriction, super.name);
+
+ @override
+ String spaceToText(
+ Map<Key, Space> spaceFields, Map<Key, Space> additionalSpaceFields) {
+ StringBuffer buffer = new StringBuffer();
+ buffer.write(restriction.typeArgumentsText);
+ buffer.write('{');
+
+ bool first = true;
+ additionalSpaceFields.forEach((Key key, Space space) {
+ if (!first) buffer.write(', ');
+ buffer.write('$key: $space');
+ first = false;
+ });
+ if (restriction.hasRest) {
+ if (!first) buffer.write(', ');
+ buffer.write('...');
+ }
+
+ buffer.write('}');
+ return buffer.toString();
+ }
+
+ @override
+ void witnessToText(StringBuffer buffer, FieldWitness witness,
+ Map<Key, FieldWitness> witnessFields) {
+ buffer.write('{');
+ String comma = '';
+ for (MapKey key in restriction.keys) {
+ buffer.write(comma);
+ buffer.write(key.valueAsText);
+ buffer.write(': ');
+ FieldWitness? witness = witnessFields[key];
+ if (witness != null) {
+ witness.witnessToText(buffer);
+ } else {
+ buffer.write('_');
+ }
+ comma = ', ';
+ }
+ if (restriction.hasRest) {
+ buffer.write(comma);
+ buffer.write('...');
+ }
+ buffer.write('}');
+
+ // If we have restrictions on the record type we create an and pattern.
+ String additionalStart = ' && Object(';
+ String additionalEnd = '';
+ comma = '';
+ for (MapEntry<Key, FieldWitness> entry in witnessFields.entries) {
+ Key key = entry.key;
+ if (key is! MapKey) {
+ buffer.write(additionalStart);
+ additionalStart = '';
+ additionalEnd = ')';
+ buffer.write(comma);
+ comma = ', ';
+
+ buffer.write(key.name);
+ buffer.write(': ');
+ FieldWitness field = entry.value;
+ field.witnessToText(buffer);
+ }
+ }
+ buffer.write(additionalEnd);
+ }
+}
+
+/// Identity object used for creating a unique [MapPatternStaticType] for a
+/// map pattern.
+///
+/// The uniqueness is defined by the key and value types, the key values of
+/// the map pattern, and whether the map pattern has a rest element.
+///
+/// This identity ensures that we can detect overlap between map patterns with
+/// the same set of keys.
+class MapTypeIdentity<Type extends Object> implements Restriction<Type> {
+ final Type keyType;
+ final Type valueType;
+ final Set<MapKey> keys;
+ final bool hasRest;
+ final String typeArgumentsText;
+
+ MapTypeIdentity(
+ this.keyType, this.valueType, this.keys, this.typeArgumentsText,
+ {required this.hasRest});
+
+ @override
+ late final int hashCode =
+ Object.hash(keyType, valueType, Object.hashAllUnordered(keys), hasRest);
+
+ @override
+ bool get isUnrestricted {
+ // The map pattern containing only a rest pattern covers the whole type.
+ return hasRest && keys.isEmpty;
+ }
+
+ @override
+ bool operator ==(Object other) {
+ if (identical(this, other)) return true;
+ if (other is! MapTypeIdentity<Type>) return false;
+ if (keyType != other.keyType ||
+ valueType != other.valueType ||
+ hasRest != other.hasRest) {
+ return false;
+ }
+ if (keys.length != other.keys.length) return false;
+ return keys.containsAll(other.keys);
+ }
+
+ @override
+ bool isSubtypeOf(TypeOperations<Type> typeOperations, Restriction other) {
+ if (other.isUnrestricted) return true;
+ if (other is! MapTypeIdentity<Type>) return false;
+ if (!typeOperations.isSubtypeOf(keyType, other.keyType)) return false;
+ if (!typeOperations.isSubtypeOf(valueType, other.valueType)) return false;
+ if (other.hasRest) {
+ return keys.containsAll(other.keys);
+ } else if (hasRest) {
+ return false;
+ } else {
+ return keys.length == other.keys.length && keys.containsAll(other.keys);
+ }
+ }
+
+ @override
+ String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.write(typeArgumentsText);
+ sb.write('{');
+ String comma = '';
+ for (MapKey key in keys) {
+ sb.write(comma);
+ sb.write(key);
+ sb.write(': ()');
+ comma = ', ';
+ }
+ if (hasRest) {
+ sb.write(comma);
+ sb.write('...');
+ comma = ', ';
+ }
+ sb.write('}');
+ return sb.toString();
+ }
+}
diff --git a/_fe_analyzer_shared/lib/src/exhaustiveness/types/record.dart b/_fe_analyzer_shared/lib/src/exhaustiveness/types/record.dart
new file mode 100644
index 0000000..a7a93a3
--- /dev/null
+++ b/_fe_analyzer_shared/lib/src/exhaustiveness/types/record.dart
@@ -0,0 +1,146 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+part of '../types.dart';
+
+/// [StaticType] for a record type.
+///
+/// This models that type aspect of the record using only the structure of the
+/// record type. This means that the type for `(Object, String)` and
+/// `(String, int)` will be subtypes of each other.
+///
+/// This is necessary to avoid invalid conclusions on the disjointness of
+/// spaces base on the their types. For instance in
+///
+/// method((String, Object) o) {
+/// if (o case (Object _, String s)) {}
+/// }
+///
+/// the case is not empty even though `(String, Object)` and `(Object, String)`
+/// are not related type-wise.
+///
+/// Not that the fields of the record types _are_ using the type, so that
+/// the `$1` field of `(String, Object)` is known to contain only `String`s.
+class RecordStaticType<Type extends Object> extends TypeBasedStaticType<Type> {
+ RecordStaticType(super.typeOperations, super.fieldLookup, super.type);
+
+ @override
+ bool get isRecord => true;
+
+ @override
+ bool isSubtypeOfInternal(StaticType other) {
+ if (other is! RecordStaticType<Type>) {
+ return false;
+ }
+ if (fields.length != other.fields.length) {
+ return false;
+ }
+ for (MapEntry<Key, StaticType> field in fields.entries) {
+ StaticType? type = other.fields[field.key];
+ if (type == null) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @override
+ String spaceToText(
+ Map<Key, Space> spaceFields, Map<Key, Space> additionalSpaceFields) {
+ StringBuffer buffer = new StringBuffer();
+ buffer.write('(');
+ String comma = '';
+ fields.forEach((Key key, StaticType staticType) {
+ if (key is RecordIndexKey) {
+ buffer.write(comma);
+ comma = ', ';
+ buffer.write('${spaceFields[key] ?? staticType}');
+ } else if (key is RecordNameKey) {
+ buffer.write(comma);
+ comma = ', ';
+ buffer.write('${key.name}: ${spaceFields[key] ?? staticType}');
+ }
+ });
+ buffer.write(')');
+ String additionalStart = '(';
+ String additionalEnd = '';
+ comma = '';
+ spaceFields.forEach((Key key, Space value) {
+ if (key is! RecordKey) {
+ buffer.write(additionalStart);
+ additionalStart = '';
+ additionalEnd = ')';
+ buffer.write(comma);
+ comma = ', ';
+ buffer.write('${key.name}: ${value}');
+ }
+ });
+ additionalSpaceFields.forEach((Key key, Space value) {
+ if (key is! RecordKey) {
+ buffer.write(additionalStart);
+ additionalStart = '';
+ additionalEnd = ')';
+ buffer.write(comma);
+ comma = ', ';
+ buffer.write('${key.name}: ${value}');
+ }
+ });
+ buffer.write(additionalEnd);
+ return buffer.toString();
+ }
+
+ @override
+ void witnessToText(StringBuffer buffer, FieldWitness witness,
+ Map<Key, FieldWitness> witnessFields) {
+ buffer.write('(');
+ String comma = '';
+ for (Key key in fields.keys) {
+ if (key is RecordIndexKey) {
+ buffer.write(comma);
+ comma = ', ';
+
+ FieldWitness? field = witnessFields[key];
+ if (field != null) {
+ field.witnessToText(buffer);
+ } else {
+ buffer.write('_');
+ }
+ } else if (key is RecordNameKey) {
+ buffer.write(comma);
+ comma = ', ';
+
+ buffer.write(key.name);
+ buffer.write(': ');
+ FieldWitness? field = witnessFields[key];
+ if (field != null) {
+ field.witnessToText(buffer);
+ } else {
+ buffer.write('_');
+ }
+ }
+ }
+ buffer.write(')');
+
+ // If we have restrictions on the record type we create an and pattern.
+ String additionalStart = ' && Object(';
+ String additionalEnd = '';
+ comma = '';
+ for (MapEntry<Key, FieldWitness> entry in witnessFields.entries) {
+ Key key = entry.key;
+ if (key is! RecordKey) {
+ buffer.write(additionalStart);
+ additionalStart = '';
+ additionalEnd = ')';
+ buffer.write(comma);
+ comma = ', ';
+
+ buffer.write(key.name);
+ buffer.write(': ');
+ FieldWitness field = entry.value;
+ field.witnessToText(buffer);
+ }
+ }
+ buffer.write(additionalEnd);
+ }
+}
diff --git a/_fe_analyzer_shared/lib/src/exhaustiveness/types/sealed.dart b/_fe_analyzer_shared/lib/src/exhaustiveness/types/sealed.dart
new file mode 100644
index 0000000..5cdafa9
--- /dev/null
+++ b/_fe_analyzer_shared/lib/src/exhaustiveness/types/sealed.dart
@@ -0,0 +1,108 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+part of '../types.dart';
+
+/// Interface implemented by analyzer/CFE to support [StaticType]s for sealed
+/// classes.
+abstract class SealedClassOperations<Type extends Object,
+ Class extends Object> {
+ /// Returns the sealed class declaration for [type] or `null` if [type] is not
+ /// a sealed class type.
+ Class? getSealedClass(Type type);
+
+ /// Returns the direct subclasses of [sealedClass] that either extend,
+ /// implement or mix it in.
+ List<Class> getDirectSubclasses(Class sealedClass);
+
+ /// Returns the instance of [subClass] that implements [sealedClassType].
+ ///
+ /// `null` might be returned if [subClass] cannot implement [sealedClassType].
+ /// For instance
+ ///
+ /// sealed class A<T> {}
+ /// class B<T> extends A<T> {}
+ /// class C extends A<int> {}
+ ///
+ /// here `C` has no implementation of `A<String>`.
+ ///
+ /// It is assumed that `TypeOperations.isSealedClass` is `true` for
+ /// [sealedClassType] and that [subClass] is in `getDirectSubclasses` for
+ /// `getSealedClass` of [sealedClassType].
+ Type? getSubclassAsInstanceOf(Class subClass, Type sealedClassType);
+}
+
+/// [SealedClassInfo] stores information to compute the static type for a
+/// sealed class.
+class SealedClassInfo<Type extends Object, Class extends Object> {
+ final SealedClassOperations<Type, Class> _sealedClassOperations;
+ final Class _sealedClass;
+ List<Class>? _subClasses;
+
+ SealedClassInfo(this._sealedClassOperations, this._sealedClass);
+
+ /// Returns the classes that directly extends, implements or mix in
+ /// [_sealedClass].
+ Iterable<Class> get subClasses =>
+ _subClasses ??= _sealedClassOperations.getDirectSubclasses(_sealedClass);
+}
+
+/// [StaticType] for a sealed class type.
+class SealedClassStaticType<Type extends Object, Class extends Object>
+ extends TypeBasedStaticType<Type> {
+ final ExhaustivenessCache<Type, dynamic, dynamic, dynamic, Class> _cache;
+ final SealedClassOperations<Type, Class> _sealedClassOperations;
+ final SealedClassInfo<Type, Class> _sealedInfo;
+ Iterable<StaticType>? _subtypes;
+
+ SealedClassStaticType(super.typeOperations, super.fieldLookup, super.type,
+ this._cache, this._sealedClassOperations, this._sealedInfo);
+
+ @override
+ bool get isSealed => true;
+
+ @override
+ Iterable<StaticType> getSubtypes(Set<Key> keysOfInterest) =>
+ _subtypes ??= _createSubtypes();
+
+ List<StaticType> _createSubtypes() {
+ List<StaticType> subtypes = [];
+ for (Class subClass in _sealedInfo.subClasses) {
+ Type? subtype =
+ _sealedClassOperations.getSubclassAsInstanceOf(subClass, _type);
+ if (subtype != null) {
+ if (!_typeOperations.isGeneric(subtype)) {
+ // If the subtype is not generic, we can test whether it can be an
+ // actual value of [_type] by testing whether it is a subtype of the
+ // overapproximation of [_type].
+ //
+ // For instance
+ //
+ // sealed class A<T> {}
+ // class B extends A<num> {}
+ // class C<T extends num> A<T> {}
+ //
+ // method<T extends String>(A<T> a) {
+ // switch (a) {
+ // case B: // Not needed, B cannot inhabit A<T>.
+ // case C: // Needed, C<Never> inhabits A<T>.
+ // }
+ // }
+ if (!_typeOperations.isSubtypeOf(
+ subtype, _typeOperations.overapproximate(_type))) {
+ continue;
+ }
+ }
+ StaticType staticType = _cache.getStaticType(subtype);
+ // Since the type of the [subtype] might not itself be a subtype of
+ // [_type], for instance in the example above the type of `case C:`,
+ // `C<num>`, is not a subtype of `A<T>`, we wrap the static type
+ // to establish the subtype relation between the [StaticType] for the
+ // enum element and this [StaticType].
+ subtypes.add(new WrappedStaticType(staticType, this));
+ }
+ }
+ return subtypes;
+ }
+}
diff --git a/_fe_analyzer_shared/lib/src/exhaustiveness/witness.dart b/_fe_analyzer_shared/lib/src/exhaustiveness/witness.dart
index 747cce9..a159687 100644
--- a/_fe_analyzer_shared/lib/src/exhaustiveness/witness.dart
+++ b/_fe_analyzer_shared/lib/src/exhaustiveness/witness.dart
@@ -1,261 +1,18 @@
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-import 'exhaustive.dart';
-import 'space.dart';
+
+import 'key.dart';
+import 'path.dart';
import 'static_type.dart';
-/// Returns `true` if [caseSpaces] exhaustively covers all possible values of
-/// [valueSpace].
-bool isExhaustiveNew(Space valueSpace, List<Space> caseSpaces) {
- return checkExhaustiveness(valueSpace, caseSpaces) == null;
-}
-
-/// Checks the [cases] representing a series of switch cases to see if they
-/// exhaustively cover all possible values of the matched [valueType]. Also
-/// checks to see if any case can't be matched because it's covered by previous
-/// cases.
-///
-/// Returns a list of any unreachable case or non-exhaustive match errors.
-/// Returns an empty list if all cases are reachable and the cases are
-/// exhaustive.
-List<ExhaustivenessError> reportErrorsNew(
- StaticType valueType, List<Space> caseSpaces) {
- List<ExhaustivenessError> errors = <ExhaustivenessError>[];
-
- Pattern valuePattern = new Pattern(valueType, {}, []);
- List<List<Pattern>> cases =
- caseSpaces.map((space) => [_spaceToPattern(space)]).toList();
-
- for (int i = 1; i < cases.length; i++) {
- // See if this case is covered by previous ones.
- if (_unmatched(cases.sublist(0, i), cases[i]) == null) {
- errors.add(new UnreachableCaseErrorNew(valueType, caseSpaces, i));
- }
- }
-
- String? witness = _unmatched(cases, [valuePattern]);
- if (witness != null) {
- errors.add(new NonExhaustiveErrorNew(valueType, caseSpaces, witness));
- }
-
- return errors;
-}
-
-/// Determines if [caseSpaces] is exhaustive over all values contained by
-/// [valueSpace]. If so, returns `null`. Otherwise, returns a string describing
-/// an example of one value that isn't matched by anything in [caseSpaces].
-// TODO(johnniwinther): Remove this method?
-String? checkExhaustiveness(Space valueSpace, List<Space> caseSpaces) {
- Pattern value = _spaceToPattern(valueSpace);
- List<List<Pattern>> cases =
- caseSpaces.map((space) => [_spaceToPattern(space)]).toList();
-
- String? witness = _unmatched(cases, [value]);
-
- // Uncomment this to have it print out the witness for non-exhaustive matches.
- // if (witness != null) print(witness);
-
- return witness;
-}
-
-/// Convert the prototype's original [Space] representation to the [Pattern]
-/// representation used here.
-///
-/// This is only a convenience to run the existing test code which creates
-/// [Space]s using the new algorithm.
-Pattern _spaceToPattern(Space space, [List<String> path = const []]) {
- if (space is! ExtractSpace) {
- // TODO(johnniwinther): Remove this together with the old algorithm:
- // The old algorithm creates UnionSpaces internally but they are never used
- // directly as entrypoint arguments, so this function support them.
- throw new ArgumentError('Space should be an ExtractSpace.');
- }
-
- Map<String, Pattern> fields = {
- for (String name in space.fields.keys)
- name: _spaceToPattern(space.fields[name]!, [...path, name])
- };
- return new Pattern(space.type, fields, path);
-}
-
-/// Tries to find a pattern containing at least one value matched by
-/// [valuePatterns] that is not matched by any of the patterns in [caseRows].
-///
-/// If found, returns it. This is a witness example showing that [caseRows] is
-/// not exhaustive over all values in [valuePatterns]. If it returns `null`,
-/// then [caseRows] exhaustively covers [valuePatterns].
-String? _unmatched(List<List<Pattern>> caseRows, List<Pattern> valuePatterns,
- [List<Predicate> witnessPredicates = const []]) {
- // If there are no more columns, then we've tested all the predicates we have
- // to test.
- if (valuePatterns.isEmpty) {
- // If there are still any rows left, then it means every remaining value
- // will go to one of those rows' bodies, so we have successfully matched.
- if (caseRows.isNotEmpty) return null;
-
- // If we ran out of rows too, then it means [witnessPredicates] is now a
- // complete description of at least one value that slipped past all the
- // rows.
- return _witnessString(witnessPredicates);
- }
-
- // Look down the first column of tests.
- Pattern valuePattern = valuePatterns[0];
-
- // TODO(johnniwinther): Right now, this brute force expands all subtypes of
- // sealed types and considers them individually. It would be faster to look
- // at the types of the patterns in the first column of each row and only
- // expand subtypes that are actually tested.
- // Split the type into its sealed subtypes and consider each one separately.
- // This enables it to filter rows more effectively.
- List<StaticType> subtypes = _expandSealedSubtypes(valuePattern.type);
- for (StaticType subtype in subtypes) {
- String? result =
- _filterByType(subtype, caseRows, valuePatterns, witnessPredicates);
-
- // If we found a witness for a subtype that no rows match, then we can
- // stop. There may be others but we don't need to find more.
- if (result != null) return result;
- }
-
- // If we get here, no subtype yielded a witness, so we must have matched
- // everything.
- return null;
-}
-
-String? _filterByType(StaticType type, List<List<Pattern>> caseRows,
- List<Pattern> valuePatterns, List<Predicate> witnessPredicates) {
- // Extend the witness with the type we're matching.
- List<Predicate> extendedWitness = [
- ...witnessPredicates,
- new Predicate(valuePatterns.first.path, type)
- ];
-
- // Discard any row that may not match by type. We only keep rows that *must*
- // match because a row that could potentially fail to match will not help us
- // prove exhaustiveness.
- List<List<Pattern>> remainingRows = <List<Pattern>>[];
- for (List<Pattern> row in caseRows) {
- Pattern firstPattern = row[0];
-
- // If the row's type is a supertype of the value pattern's type then it
- // must match.
- if (type.isSubtypeOf(firstPattern.type)) {
- remainingRows.add(row);
- }
- }
-
- // We have now filtered by the type test of the first column of patterns, but
- // some of those may also have field subpatterns. If so, lift those out so we
- // can recurse into them.
- Set<String> fieldNames = {
- ...valuePatterns.first.fields.keys,
- for (List<Pattern> row in remainingRows) ...row.first.fields.keys
- };
-
- // Sorting isn't necessary, but makes the behavior deterministic.
- List<String> sorted = fieldNames.toList()..sort();
-
- // Remove the first column from the value list and replace it with any
- // expanded fields.
- valuePatterns = [
- ..._expandFields(sorted, valuePatterns.first, type),
- ...valuePatterns.skip(1)
- ];
-
- // Remove the first column from each row and replace it with any expanded
- // fields.
- for (int i = 0; i < remainingRows.length; i++) {
- remainingRows[i] = [
- ..._expandFields(
- sorted, remainingRows[i].first, remainingRows[i].first.type),
- ...remainingRows[i].skip(1)
- ];
- }
-
- // Proceed to the next column.
- return _unmatched(remainingRows, valuePatterns, extendedWitness);
-}
-
-/// Given a list of [fieldNames] and a [pattern], generates a list of patterns,
-/// one for each named field.
-///
-/// When pattern contains a field with that name, extracts it into the
-/// resulting list. Otherwise, the pattern doesn't care
-/// about that field, so inserts a default pattern that matches all values for
-/// the field.
-///
-/// In other words, this unpacks a set of fields so that the main algorithm can
-/// add them to the worklist.
-List<Pattern> _expandFields(
- List<String> fieldNames, Pattern pattern, StaticType type) {
- List<Pattern> result = <Pattern>[];
- for (String fieldName in fieldNames) {
- Pattern? field = pattern.fields[fieldName];
- if (field != null) {
- result.add(field);
- } else {
- // This pattern doesn't test this field, so add a pattern for the
- // field that matches all values. This way the columns stay aligned.
- result.add(
- new Pattern(
- type.fields[fieldName] ?? StaticType.nullableObject,
- {},
- [...pattern.path, fieldName],
- ),
- );
- }
- }
-
- return result;
-}
-
-/// Recursively expands [type] with its subtypes if its sealed.
-///
-/// Otherwise, just returns [type].
-List<StaticType> _expandSealedSubtypes(StaticType type) {
- if (!type.isSealed) return [type];
-
- return {
- for (StaticType subtype in type.subtypes) ..._expandSealedSubtypes(subtype)
- }.toList();
-}
-
-/// The main pattern for matching types and destructuring.
-///
-/// It has a type which determines the type of values it contains. The type may
-/// be [StaticType.top] to indicate that it doesn't filter by type.
-///
-/// It may also contain zero or more named fields. The pattern then only matches
-/// values where the field values are matched by the corresponding field
-/// patterns.
-// TODO(johnniwinther): Rename this to avoid name clash with Pattern from
-// dart:core.
-class Pattern {
- /// The type of values the pattern matches.
- final StaticType type;
-
- /// Any field subpatterns the pattern matches.
- final Map<String, Pattern> fields;
-
- /// The path of getters that led from the original matched value to value
- /// matched by this pattern. Used to generate a human-readable witness.
- final List<String> path;
-
- Pattern(this.type, this.fields, this.path);
-
- @override
- String toString() => 'Pattern(type=$type,fields=$fields,path=$path)';
-}
-
/// Describes a pattern that matches the value or a field accessed from it.
///
/// Used only to generate the witness description.
class Predicate {
/// The path of getters that led from the original matched value to the value
/// tested by this predicate.
- final List<String> path;
+ final Path path;
/// The type this predicate tests.
// TODO(johnniwinther): In order to model exhaustiveness on enum types,
@@ -269,8 +26,10 @@
String toString() => 'Predicate(path=$path,type=$type)';
}
-/// Builds a human-friendly pattern-like string for the witness matched by
-/// [predicates].
+/// Witness that show an unmatched case.
+///
+/// This is used to builds a human-friendly pattern-like string for the witness
+/// matched by [_predicates].
///
/// For example, given:
///
@@ -282,54 +41,45 @@
/// ['z', 'x'] is C
/// ['z', 'y'] is B
///
-/// Produces:
+/// the [toString] produces:
///
/// 'U(w: T(x: B, y: B), z: T(x: C, y: B))'
-String _witnessString(List<Predicate> predicates) {
- Witness witness = new Witness();
+class Witness {
+ final List<Predicate> _predicates;
+ late final FieldWitness _witness = _buildWitness();
- for (Predicate predicate in predicates) {
- Witness here = witness;
- for (String field in predicate.path) {
- here = here.fields.putIfAbsent(field, () => new Witness());
+ Witness(this._predicates);
+
+ FieldWitness _buildWitness() {
+ FieldWitness witness = new FieldWitness();
+
+ for (Predicate predicate in _predicates) {
+ FieldWitness here = witness;
+ for (Key field in predicate.path.toList()) {
+ here = here.fields.putIfAbsent(field, () => new FieldWitness());
+ }
+ here.type = predicate.type;
}
- here.type = predicate.type;
+ return witness;
}
- StringBuffer buffer = new StringBuffer();
- witness.buildString(buffer);
- return buffer.toString();
+ @override
+ String toString() => _witness.toString();
}
/// Helper class used to turn a list of [Predicates] into a string.
-class Witness {
+class FieldWitness {
StaticType type = StaticType.nullableObject;
- final Map<String, Witness> fields = {};
+ final Map<Key, FieldWitness> fields = {};
- void buildString(StringBuffer buffer) {
- if (type != StaticType.nullableObject) {
- buffer.write(type);
- }
-
- if (fields.isNotEmpty) {
- buffer.write('(');
- bool first = true;
- fields.forEach((name, field) {
- if (!first) buffer.write(', ');
- first = false;
-
- buffer.write(name);
- buffer.write(': ');
- field.buildString(buffer);
- });
- buffer.write(')');
- }
+ void witnessToText(StringBuffer buffer) {
+ type.witnessToText(buffer, this, fields);
}
@override
String toString() {
StringBuffer sb = new StringBuffer();
- buildString(sb);
+ witnessToText(sb);
return sb.toString();
}
}
diff --git a/_fe_analyzer_shared/lib/src/experiments/flags.dart b/_fe_analyzer_shared/lib/src/experiments/flags.dart
index f1125e5..cb1f396 100644
--- a/_fe_analyzer_shared/lib/src/experiments/flags.dart
+++ b/_fe_analyzer_shared/lib/src/experiments/flags.dart
@@ -11,7 +11,7 @@
enum ExperimentalFlag {
classModifiers(
name: 'class-modifiers',
- isEnabledByDefault: false,
+ isEnabledByDefault: true,
isExpired: false,
experimentEnabledVersion: const Version(3, 0),
experimentReleasedVersion: const Version(3, 0)),
@@ -123,21 +123,21 @@
patterns(
name: 'patterns',
- isEnabledByDefault: false,
+ isEnabledByDefault: true,
isExpired: false,
experimentEnabledVersion: const Version(3, 0),
experimentReleasedVersion: const Version(3, 0)),
records(
name: 'records',
- isEnabledByDefault: false,
+ isEnabledByDefault: true,
isExpired: false,
experimentEnabledVersion: const Version(3, 0),
- experimentReleasedVersion: const Version(2, 19)),
+ experimentReleasedVersion: const Version(3, 0)),
sealedClass(
name: 'sealed-class',
- isEnabledByDefault: false,
+ isEnabledByDefault: true,
isExpired: false,
experimentEnabledVersion: const Version(3, 0),
experimentReleasedVersion: const Version(3, 0)),
diff --git a/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart b/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart
index 143f407..c2be3f9 100644
--- a/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart
+++ b/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart
@@ -241,8 +241,6 @@
///
/// [matchedType] should be the static type of the value being matched.
/// [staticType] should be the static type of the variable pattern itself.
- /// [initializerExpression] should be the initializer expression being matched
- /// (or `null` if there is no expression being matched to this variable).
/// [isFinal] indicates whether the variable is final, and [isImplicitlyTyped]
/// indicates whether the variable has an explicit type annotation.
///
@@ -257,7 +255,6 @@
int declaredVariablePattern(
{required Type matchedType,
required Type staticType,
- Expression? initializerExpression,
bool isFinal = false,
bool isLate = false,
required bool isImplicitlyTyped});
@@ -1185,19 +1182,16 @@
int declaredVariablePattern(
{required Type matchedType,
required Type staticType,
- Expression? initializerExpression,
bool isFinal = false,
bool isLate = false,
required bool isImplicitlyTyped}) {
return _wrap(
'declaredVariablePattern(matchedType: $matchedType, '
- 'staticType: $staticType, '
- 'initializerExpression: $initializerExpression, isFinal: $isFinal, '
+ 'staticType: $staticType, isFinal: $isFinal, '
'isLate: $isLate, isImplicitlyTyped: $isImplicitlyTyped)',
() => _wrapped.declaredVariablePattern(
matchedType: matchedType,
staticType: staticType,
- initializerExpression: initializerExpression,
isFinal: isFinal,
isLate: isLate,
isImplicitlyTyped: isImplicitlyTyped),
@@ -3680,27 +3674,6 @@
/// [ReferenceWithType] object referring to the scrutinee. Otherwise `null`.
ReferenceWithType<Type>? _scrutineeReference;
- /// If a pattern is being analyzed, and the scrutinee is something that might
- /// be type promoted as a consequence of the pattern match, [SsaNode]
- /// reflecting the state of the pattern match at the time that
- /// [_scrutineeReference] was captured. Otherwise `null`.
- ///
- /// This is necessary to detect situations where the scrutinee is modified
- /// after the beginning of a switch statement and before choosing the case to
- /// execute (e.g. in a guard clause), and therefore further pattern matches
- /// should not promote the scrutinee (since they are acting on a cached value
- /// that no longer matches the scrutinee expression). For example:
- ///
- /// switch (v) {
- /// case int _: // promotes `v` to `int`
- /// break;
- /// case _ when f(v = ...): // reassigns `v`
- /// break;
- /// case String _: // does not promote `v` to `String`
- /// break;
- /// }
- SsaNode<Type>? _scrutineeSsaNode;
-
/// The most recently visited expression for which an [ExpressionInfo] object
/// exists, or `null` if no expression has been visited that has a
/// corresponding [ExpressionInfo] object.
@@ -3891,7 +3864,6 @@
int declaredVariablePattern(
{required Type matchedType,
required Type staticType,
- Expression? initializerExpression,
bool isFinal = false,
bool isLate = false,
required bool isImplicitlyTyped}) {
@@ -3997,7 +3969,6 @@
assert(_current.reachable.parent == null);
assert(_unmatched == null);
assert(_scrutineeReference == null);
- assert(_scrutineeSsaNode == null);
}
@override
@@ -4130,7 +4101,8 @@
// Note that we don't need to take any action to handle
// `before(E1) = matched(P)`, because we store both the "matched" state for
// patterns and the "before" state for expressions in `_current`.
- _pushPattern(_pushScrutinee(scrutinee, scrutineeType));
+ _pushPattern(_pushScrutinee(scrutinee, scrutineeType,
+ allowScrutineePromotion: true));
}
@override
@@ -4467,7 +4439,7 @@
@override
void patternAssignment_afterRhs(Expression rhs, Type rhsType) {
- _pushPattern(_pushScrutinee(rhs, rhsType));
+ _pushPattern(_pushScrutinee(rhs, rhsType, allowScrutineePromotion: false));
}
@override
@@ -4478,7 +4450,8 @@
@override
void patternForIn_afterExpression(Type elementType) {
- _pushPattern(_pushScrutinee(null, elementType));
+ _pushPattern(
+ _pushScrutinee(null, elementType, allowScrutineePromotion: false));
}
@override
@@ -4490,7 +4463,8 @@
@override
void patternVariableDeclaration_afterInitializer(
Expression initializer, Type initializerType) {
- _pushPattern(_pushScrutinee(initializer, initializerType));
+ _pushPattern(_pushScrutinee(initializer, initializerType,
+ allowScrutineePromotion: false));
}
@override
@@ -4526,6 +4500,13 @@
required Type knownType,
bool matchFailsIfWrongType = true,
bool matchMayFailEvenIfCorrectType = false}) {
+ if (operations.classifyType(matchedType) ==
+ TypeClassification.nonNullable) {
+ // The matched type is non-nullable, so promote to a non-nullable type.
+ // This allows for code like `case int? x?` to promote `x` to
+ // non-nullable.
+ knownType = operations.promoteToNonNull(knownType);
+ }
_PatternContext<Type> context = _stack.last as _PatternContext<Type>;
ReferenceWithType<Type> matchedValueReference =
context.createReference(matchedType);
@@ -4694,7 +4675,7 @@
void switchStatement_expressionEnd(
Statement? switchStatement, Expression scrutinee, Type scrutineeType) {
EqualityInfo<Type> matchedValueInfo =
- _pushScrutinee(scrutinee, scrutineeType);
+ _pushScrutinee(scrutinee, scrutineeType, allowScrutineePromotion: true);
_current = _current.split();
_SwitchStatementContext<Type> context = new _SwitchStatementContext<Type>(
_current.reachable.parent!, _current, matchedValueInfo);
@@ -4880,9 +4861,6 @@
if (_scrutineeReference != null) {
print(' scrutineeReference: $_scrutineeReference');
}
- if (_scrutineeSsaNode != null) {
- print(' scrutineeSsaNode: $_scrutineeSsaNode');
- }
if (_expressionWithInfo != null) {
print(' expressionWithInfo: $_expressionWithInfo');
}
@@ -5250,7 +5228,6 @@
_ScrutineeContext<Type> context =
_stack.removeLast() as _ScrutineeContext<Type>;
_scrutineeReference = context.previousScrutineeReference;
- _scrutineeSsaNode = context.previousScrutineeSsaNode;
}
/// Updates the [_stack] to reflect the fact that flow analysis is entering
@@ -5271,25 +5248,32 @@
/// that's being matched directly, as happens when in `for-in` loops).
/// [scrutineeType] should be the static type of the scrutinee.
///
+ /// [allowScrutineePromotion] indicates whether pattern matches should cause
+ /// the scrutinee to be promoted.
+ ///
/// The returned value is the [EqualityInfo] representing the value being
/// matched. It should be passed to [_pushPattern].
- EqualityInfo<Type> _pushScrutinee(Expression? scrutinee, Type scrutineeType) {
+ EqualityInfo<Type> _pushScrutinee(Expression? scrutinee, Type scrutineeType,
+ {required bool allowScrutineePromotion}) {
EqualityInfo<Type>? scrutineeInfo = scrutinee == null
? null
: _computeEqualityInfo(scrutinee, scrutineeType);
_stack.add(new _ScrutineeContext<Type>(
- previousScrutineeReference: _scrutineeReference,
- previousScrutineeSsaNode: _scrutineeSsaNode));
+ previousScrutineeReference: _scrutineeReference));
ReferenceWithType<Type>? scrutineeReference = scrutineeInfo?._reference;
_scrutineeReference = scrutineeReference;
- _scrutineeSsaNode = scrutineeReference == null
- ? new SsaNode<Type>(null)
- : _current.infoFor(scrutineeReference.promotionKey).ssaNode;
+ SsaNode<Type>? scrutineeSsaNode;
+ if (!allowScrutineePromotion || scrutineeReference == null) {
+ scrutineeSsaNode = new SsaNode<Type>(null);
+ } else {
+ scrutineeSsaNode =
+ _current.infoFor(scrutineeReference.promotionKey).ssaNode;
+ }
return new EqualityInfo._(
scrutineeInfo?._expressionInfo,
scrutineeType,
new ReferenceWithType(
- _makeTemporaryReference(_scrutineeSsaNode), scrutineeType,
+ _makeTemporaryReference(scrutineeSsaNode), scrutineeType,
isPromotable: true, isThisOrSuper: false));
}
@@ -5577,7 +5561,6 @@
int declaredVariablePattern(
{required Type matchedType,
required Type staticType,
- Expression? initializerExpression,
bool isFinal = false,
bool isLate = false,
required bool isImplicitlyTyped}) =>
@@ -6248,16 +6231,11 @@
class _ScrutineeContext<Type extends Object> extends _FlowContext {
final ReferenceWithType<Type>? previousScrutineeReference;
- final SsaNode<Type>? previousScrutineeSsaNode;
-
- _ScrutineeContext(
- {required this.previousScrutineeReference,
- required this.previousScrutineeSsaNode});
+ _ScrutineeContext({required this.previousScrutineeReference});
@override
Map<String, Object?> get _debugFields => super._debugFields
- ..['previousScrutineeReference'] = previousScrutineeReference
- ..['previousScrutineeSsaNode'] = previousScrutineeSsaNode;
+ ..['previousScrutineeReference'] = previousScrutineeReference;
@override
String get _debugType => '_ScrutineeContext';
diff --git a/_fe_analyzer_shared/lib/src/macros/api/introspection.dart b/_fe_analyzer_shared/lib/src/macros/api/introspection.dart
index a9e6b74..21e2f57 100644
--- a/_fe_analyzer_shared/lib/src/macros/api/introspection.dart
+++ b/_fe_analyzer_shared/lib/src/macros/api/introspection.dart
@@ -117,18 +117,33 @@
///
/// All type declarations which can have members will have a variant which
/// implements this type.
-mixin IntrospectableType implements TypeDeclaration {}
+abstract class IntrospectableType implements TypeDeclaration {}
-/// Class (and enum) introspection information.
+/// Class introspection information.
///
/// Information about fields, methods, and constructors must be retrieved from
/// the `builder` objects.
abstract class ClassDeclaration implements ParameterizedTypeDeclaration {
/// Whether this class has an `abstract` modifier.
- bool get isAbstract;
+ bool get hasAbstract;
+
+ /// Whether this class has a `base` modifier.
+ bool get hasBase;
/// Whether this class has an `external` modifier.
- bool get isExternal;
+ bool get hasExternal;
+
+ /// Whether this class has a `final` modifier.
+ bool get hasFinal;
+
+ /// Whether this class has an `interface` modifier.
+ bool get hasInterface;
+
+ /// Whether this class has a `mixin` modifier.
+ bool get hasMixin;
+
+ /// Whether this class has a `sealed` modifier.
+ bool get hasSealed;
/// The `extends` type annotation, if present.
NamedTypeAnnotation? get superclass;
@@ -138,10 +153,6 @@
/// All the `with` type annotations.
Iterable<NamedTypeAnnotation> get mixins;
-
- /// All the type arguments, if applicable.
- @override
- Iterable<TypeParameterDeclaration> get typeParameters;
}
/// An introspectable class declaration.
diff --git a/_fe_analyzer_shared/lib/src/macros/executor/introspection_impls.dart b/_fe_analyzer_shared/lib/src/macros/executor/introspection_impls.dart
index 8a2c936..e09bd06 100644
--- a/_fe_analyzer_shared/lib/src/macros/executor/introspection_impls.dart
+++ b/_fe_analyzer_shared/lib/src/macros/executor/introspection_impls.dart
@@ -572,10 +572,25 @@
final List<NamedTypeAnnotationImpl> interfaces;
@override
- final bool isAbstract;
+ final bool hasAbstract;
@override
- final bool isExternal;
+ final bool hasBase;
+
+ @override
+ final bool hasExternal;
+
+ @override
+ final bool hasFinal;
+
+ @override
+ final bool hasInterface;
+
+ @override
+ final bool hasMixin;
+
+ @override
+ final bool hasSealed;
@override
final List<NamedTypeAnnotationImpl> mixins;
@@ -596,8 +611,13 @@
required super.typeParameters,
// ClassDeclaration fields
required this.interfaces,
- required this.isAbstract,
- required this.isExternal,
+ required this.hasAbstract,
+ required this.hasBase,
+ required this.hasExternal,
+ required this.hasFinal,
+ required this.hasInterface,
+ required this.hasMixin,
+ required this.hasSealed,
required this.mixins,
required this.superclass,
});
@@ -614,8 +634,13 @@
}
serializer
..endList()
- ..addBool(isAbstract)
- ..addBool(isExternal)
+ ..addBool(hasAbstract)
+ ..addBool(hasBase)
+ ..addBool(hasExternal)
+ ..addBool(hasFinal)
+ ..addBool(hasInterface)
+ ..addBool(hasMixin)
+ ..addBool(hasSealed)
..startList();
for (NamedTypeAnnotationImpl mixin in mixins) {
mixin.serialize(serializer);
diff --git a/_fe_analyzer_shared/lib/src/macros/executor/serialization_extensions.dart b/_fe_analyzer_shared/lib/src/macros/executor/serialization_extensions.dart
index ca06efe..aa9d96a 100644
--- a/_fe_analyzer_shared/lib/src/macros/executor/serialization_extensions.dart
+++ b/_fe_analyzer_shared/lib/src/macros/executor/serialization_extensions.dart
@@ -220,8 +220,13 @@
identifier: expectRemoteInstance(),
typeParameters: (this..moveNext())._expectRemoteInstanceList(),
interfaces: (this..moveNext())._expectRemoteInstanceList(),
- isAbstract: (this..moveNext()).expectBool(),
- isExternal: (this..moveNext()).expectBool(),
+ hasAbstract: (this..moveNext()).expectBool(),
+ hasBase: (this..moveNext()).expectBool(),
+ hasExternal: (this..moveNext()).expectBool(),
+ hasFinal: (this..moveNext()).expectBool(),
+ hasInterface: (this..moveNext()).expectBool(),
+ hasMixin: (this..moveNext()).expectBool(),
+ hasSealed: (this..moveNext()).expectBool(),
mixins: (this..moveNext())._expectRemoteInstanceList(),
superclass:
(this..moveNext()).checkNull() ? null : expectRemoteInstance(),
@@ -234,8 +239,13 @@
identifier: expectRemoteInstance(),
typeParameters: (this..moveNext())._expectRemoteInstanceList(),
interfaces: (this..moveNext())._expectRemoteInstanceList(),
- isAbstract: (this..moveNext()).expectBool(),
- isExternal: (this..moveNext()).expectBool(),
+ hasAbstract: (this..moveNext()).expectBool(),
+ hasBase: (this..moveNext()).expectBool(),
+ hasExternal: (this..moveNext()).expectBool(),
+ hasFinal: (this..moveNext()).expectBool(),
+ hasInterface: (this..moveNext()).expectBool(),
+ hasMixin: (this..moveNext()).expectBool(),
+ hasSealed: (this..moveNext()).expectBool(),
mixins: (this..moveNext())._expectRemoteInstanceList(),
superclass:
(this..moveNext()).checkNull() ? null : expectRemoteInstance(),
diff --git a/_fe_analyzer_shared/lib/src/messages/codes_generated.dart b/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
index 827b29e..5b07024 100644
--- a/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
+++ b/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
@@ -3072,9 +3072,9 @@
const MessageCode messageEmptyRecordTypeNamedFieldsList = const MessageCode(
"EmptyRecordTypeNamedFieldsList",
index: 129,
- problemMessage: r"""Record type named fields list can't be empty.""",
- correctionMessage:
- r"""Try adding a record type named field to the list.""");
+ problemMessage:
+ r"""The list of named fields in a record type can't be empty.""",
+ correctionMessage: r"""Try adding a named field to the list.""");
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Code<Null> codeEncoding = messageEncoding;
@@ -3344,6 +3344,26 @@
r"""The name 'values' is not a valid name for an enum. Try using a different name.""");
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Null> codeEqualKeysInMapPattern = messageEqualKeysInMapPattern;
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const MessageCode messageEqualKeysInMapPattern = const MessageCode(
+ "EqualKeysInMapPattern",
+ analyzerCodes: <String>["EQUAL_KEYS_IN_MAP_PATTERN"],
+ problemMessage: r"""Two keys in a map pattern can't be equal.""",
+ correctionMessage: r"""Change or remove the duplicate key.""");
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Null> codeEqualKeysInMapPatternContext =
+ messageEqualKeysInMapPatternContext;
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const MessageCode messageEqualKeysInMapPatternContext = const MessageCode(
+ "EqualKeysInMapPatternContext",
+ severity: Severity.context,
+ problemMessage: r"""This is the previous use of the same key.""");
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Code<Null> codeEqualityCannotBeEqualityOperand =
messageEqualityCannotBeEqualityOperand;
@@ -4046,16 +4066,6 @@
r"""This can't be used as an annotation; an annotation should be a reference to a compile-time constant variable, or a call to a constant constructor.""");
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
-const Code<Null> codeExtendFunction = messageExtendFunction;
-
-// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
-const MessageCode messageExtendFunction = const MessageCode("ExtendFunction",
- severity: Severity.ignored,
- problemMessage: r"""Extending 'Function' is deprecated.""",
- correctionMessage:
- r"""Try removing 'Function' from the 'extends' clause.""");
-
-// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Template<Message Function(String name)> templateExtendingEnum =
const Template<Message Function(String name)>(
problemMessageTemplate:
@@ -5297,6 +5307,15 @@
}
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Null> codeFinalMixinClass = messageFinalMixinClass;
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const MessageCode messageFinalMixinClass = const MessageCode("FinalMixinClass",
+ index: 142,
+ problemMessage: r"""A mixin class can't be declared 'final'.""",
+ correctionMessage: r"""Try removing the 'final' keyword.""");
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Template<
Message Function(
String
@@ -5654,17 +5673,6 @@
r"""Functions marked 'sync*' can't have return type 'void'.""");
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
-const Code<Null> codeImplementFunction = messageImplementFunction;
-
-// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
-const MessageCode messageImplementFunction = const MessageCode(
- "ImplementFunction",
- severity: Severity.ignored,
- problemMessage: r"""Implementing 'Function' is deprecated.""",
- correctionMessage:
- r"""Try removing 'Function' from the 'implements' clause.""");
-
-// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Code<Null> codeImplementsBeforeExtends = messageImplementsBeforeExtends;
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
@@ -6419,6 +6427,16 @@
}
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Null> codeInterfaceMixinClass = messageInterfaceMixinClass;
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const MessageCode messageInterfaceMixinClass = const MessageCode(
+ "InterfaceMixinClass",
+ index: 143,
+ problemMessage: r"""A mixin class can't be declared 'interface'.""",
+ correctionMessage: r"""Try removing the 'interface' keyword.""");
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Template<
Message Function(
String
@@ -8988,15 +9006,6 @@
problemMessage: r"""Mixins can't declare constructors.""");
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
-const Code<Null> codeMixinFunction = messageMixinFunction;
-
-// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
-const MessageCode messageMixinFunction = const MessageCode("MixinFunction",
- severity: Severity.ignored,
- problemMessage: r"""Mixing in 'Function' is deprecated.""",
- correctionMessage: r"""Try removing 'Function' from the 'with' clause.""");
-
-// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Template<
Message Function(
String
@@ -9267,6 +9276,26 @@
r"""The default case is not reachable with sound null safety because the switch expression is non-nullable.""");
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Null> codeNeverReachableSwitchExpressionError =
+ messageNeverReachableSwitchExpressionError;
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const MessageCode messageNeverReachableSwitchExpressionError = const MessageCode(
+ "NeverReachableSwitchExpressionError",
+ problemMessage:
+ r"""`null` encountered as case in a switch expression with a non-nullable type.""");
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Null> codeNeverReachableSwitchStatementError =
+ messageNeverReachableSwitchStatementError;
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const MessageCode messageNeverReachableSwitchStatementError = const MessageCode(
+ "NeverReachableSwitchStatementError",
+ problemMessage:
+ r"""`null` encountered as case in a switch statement with a non-nullable type.""");
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Code<Null> codeNeverValueError = messageNeverValueError;
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
@@ -10120,6 +10149,17 @@
problemMessage: r"""The class 'Object' can't use mixins.""");
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Null> codeObsoleteColonForDefaultValue =
+ messageObsoleteColonForDefaultValue;
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const MessageCode messageObsoleteColonForDefaultValue = const MessageCode(
+ "ObsoleteColonForDefaultValue",
+ problemMessage:
+ r"""Using a colon as a separator before a default value is no longer supported.""",
+ correctionMessage: r"""Try replacing the colon with an equal sign.""");
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Code<Null> codeOnlyTry = messageOnlyTry;
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
@@ -10881,6 +10921,14 @@
correctionMessage: r"""Try assigning to a local variable.""");
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Null> codePatternMatchingError = messagePatternMatchingError;
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const MessageCode messagePatternMatchingError = const MessageCode(
+ "PatternMatchingError",
+ problemMessage: r"""Pattern matching error""");
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Code<Null> codePlatformPrivateLibraryAccess =
messagePlatformPrivateLibraryAccess;
@@ -10963,7 +11011,7 @@
const MessageCode("RecordLiteralOnePositionalFieldNoTrailingComma",
index: 127,
problemMessage:
- r"""Record literal with one field requires a trailing comma.""",
+ r"""A record literal with exactly one positional field requires a trailing comma.""",
correctionMessage: r"""Try adding a trailing comma.""");
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
@@ -10975,7 +11023,7 @@
const MessageCode("RecordLiteralZeroFieldsWithTrailingComma",
index: 128,
problemMessage:
- r"""Record literal without fields can't have a trailing comma.""",
+ r"""A record literal without fields can't have a trailing comma.""",
correctionMessage: r"""Try removing the trailing comma.""");
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
@@ -10987,7 +11035,7 @@
const MessageCode("RecordTypeOnePositionalFieldNoTrailingComma",
index: 131,
problemMessage:
- r"""Record type with one entry requires a trailing comma.""",
+ r"""A record type with exactly one positional field requires a trailing comma.""",
correctionMessage: r"""Try adding a trailing comma.""");
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
@@ -10999,7 +11047,7 @@
const MessageCode("RecordTypeZeroFieldsButTrailingComma",
index: 130,
problemMessage:
- r"""Record type without fields can't have a trailing comma.""",
+ r"""A record type without fields can't have a trailing comma.""",
correctionMessage: r"""Try removing the trailing comma.""");
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
@@ -11307,6 +11355,16 @@
}
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Null> codeSealedMixinClass = messageSealedMixinClass;
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const MessageCode messageSealedMixinClass = const MessageCode(
+ "SealedMixinClass",
+ index: 144,
+ problemMessage: r"""A mixin class can't be declared 'sealed'.""",
+ correctionMessage: r"""Try removing the 'sealed' keyword.""");
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Template<
Message Function(
String
@@ -11639,6 +11697,76 @@
r"""Loaded library is compiled with unsound null safety and cannot be used in compilation for sound null safety.""");
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Template<
+ Message Function(
+ String name,
+ String
+ name2)> templateSubtypeOfBaseIsNotBaseFinalOrSealed = const Template<
+ Message Function(String name, String name2)>(
+ problemMessageTemplate:
+ r"""The type '#name' must be 'base', 'final' or 'sealed' because the supertype '#name2' is 'base'.""",
+ correctionMessageTemplate:
+ r"""Try adding 'base', 'final', or 'sealed' to the type.""",
+ withArguments: _withArgumentsSubtypeOfBaseIsNotBaseFinalOrSealed);
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Message Function(String name, String name2)>
+ codeSubtypeOfBaseIsNotBaseFinalOrSealed =
+ const Code<Message Function(String name, String name2)>(
+ "SubtypeOfBaseIsNotBaseFinalOrSealed",
+ analyzerCodes: <String>["SUBTYPE_OF_BASE_IS_NOT_BASE_FINAL_OR_SEALED"]);
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+Message _withArgumentsSubtypeOfBaseIsNotBaseFinalOrSealed(
+ String name, String name2) {
+ if (name.isEmpty) throw 'No name provided';
+ name = demangleMixinApplicationName(name);
+ if (name2.isEmpty) throw 'No name provided';
+ name2 = demangleMixinApplicationName(name2);
+ return new Message(codeSubtypeOfBaseIsNotBaseFinalOrSealed,
+ problemMessage:
+ """The type '${name}' must be 'base', 'final' or 'sealed' because the supertype '${name2}' is 'base'.""",
+ correctionMessage: """Try adding 'base', 'final', or 'sealed' to the type.""",
+ arguments: {'name': name, 'name2': name2});
+}
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Template<
+ Message Function(
+ String name,
+ String
+ name2)> templateSubtypeOfFinalIsNotBaseFinalOrSealed = const Template<
+ Message Function(String name, String name2)>(
+ problemMessageTemplate:
+ r"""The type '#name' must be 'base', 'final' or 'sealed' because the supertype '#name2' is 'final'.""",
+ correctionMessageTemplate:
+ r"""Try adding 'base', 'final', or 'sealed' to the type.""",
+ withArguments: _withArgumentsSubtypeOfFinalIsNotBaseFinalOrSealed);
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Message Function(String name, String name2)>
+ codeSubtypeOfFinalIsNotBaseFinalOrSealed =
+ const Code<Message Function(String name, String name2)>(
+ "SubtypeOfFinalIsNotBaseFinalOrSealed",
+ analyzerCodes: <String>[
+ "SUBTYPE_OF_FINAL_IS_NOT_BASE_FINAL_OR_SEALED"
+ ]);
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+Message _withArgumentsSubtypeOfFinalIsNotBaseFinalOrSealed(
+ String name, String name2) {
+ if (name.isEmpty) throw 'No name provided';
+ name = demangleMixinApplicationName(name);
+ if (name2.isEmpty) throw 'No name provided';
+ name2 = demangleMixinApplicationName(name2);
+ return new Message(codeSubtypeOfFinalIsNotBaseFinalOrSealed,
+ problemMessage:
+ """The type '${name}' must be 'base', 'final' or 'sealed' because the supertype '${name2}' is 'final'.""",
+ correctionMessage: """Try adding 'base', 'final', or 'sealed' to the type.""",
+ arguments: {'name': name, 'name2': name2});
+}
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Code<Null> codeSuperAsExpression = messageSuperAsExpression;
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
@@ -12983,6 +13111,64 @@
correctionMessage: r"""Try removing the type arguments.""");
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Null> codeWeakReferenceMismatchReturnAndArgumentTypes =
+ messageWeakReferenceMismatchReturnAndArgumentTypes;
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const MessageCode messageWeakReferenceMismatchReturnAndArgumentTypes =
+ const MessageCode("WeakReferenceMismatchReturnAndArgumentTypes",
+ problemMessage:
+ r"""Return and argument types of a weak reference should match.""");
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Null> codeWeakReferenceNotOneArgument =
+ messageWeakReferenceNotOneArgument;
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const MessageCode messageWeakReferenceNotOneArgument = const MessageCode(
+ "WeakReferenceNotOneArgument",
+ problemMessage:
+ r"""Weak reference should take one required positional argument.""");
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Null> codeWeakReferenceNotStatic = messageWeakReferenceNotStatic;
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const MessageCode messageWeakReferenceNotStatic = const MessageCode(
+ "WeakReferenceNotStatic",
+ problemMessage:
+ r"""Weak reference pragma can be used on a static method only.""");
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Null> codeWeakReferenceReturnTypeNotNullable =
+ messageWeakReferenceReturnTypeNotNullable;
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const MessageCode messageWeakReferenceReturnTypeNotNullable = const MessageCode(
+ "WeakReferenceReturnTypeNotNullable",
+ problemMessage: r"""Return type of a weak reference should be nullable.""");
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Null> codeWeakReferenceTargetHasParameters =
+ messageWeakReferenceTargetHasParameters;
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const MessageCode messageWeakReferenceTargetHasParameters = const MessageCode(
+ "WeakReferenceTargetHasParameters",
+ problemMessage:
+ r"""The target of weak reference should not take parameters.""");
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Null> codeWeakReferenceTargetNotStaticTearoff =
+ messageWeakReferenceTargetNotStaticTearoff;
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const MessageCode messageWeakReferenceTargetNotStaticTearoff = const MessageCode(
+ "WeakReferenceTargetNotStaticTearoff",
+ problemMessage:
+ r"""The target of weak reference should be a tearoff of a static method.""");
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Code<Null> codeWeakWithStrongDillLibrary =
messageWeakWithStrongDillLibrary;
diff --git a/_fe_analyzer_shared/lib/src/parser/forwarding_listener.dart b/_fe_analyzer_shared/lib/src/parser/forwarding_listener.dart
index b45c6a9..1bf48cb 100644
--- a/_fe_analyzer_shared/lib/src/parser/forwarding_listener.dart
+++ b/_fe_analyzer_shared/lib/src/parser/forwarding_listener.dart
@@ -2050,8 +2050,9 @@
}
@override
- void handleValuedFormalParameter(Token equals, Token token) {
- listener?.handleValuedFormalParameter(equals, token);
+ void handleValuedFormalParameter(
+ Token equals, Token token, FormalParameterKind kind) {
+ listener?.handleValuedFormalParameter(equals, token, kind);
}
@override
diff --git a/_fe_analyzer_shared/lib/src/parser/listener.dart b/_fe_analyzer_shared/lib/src/parser/listener.dart
index d21ca1b..fcafd3c 100644
--- a/_fe_analyzer_shared/lib/src/parser/listener.dart
+++ b/_fe_analyzer_shared/lib/src/parser/listener.dart
@@ -2073,7 +2073,8 @@
logEvent("FormalParameterDefaultValueExpression");
}
- void handleValuedFormalParameter(Token equals, Token token) {
+ void handleValuedFormalParameter(
+ Token equals, Token token, FormalParameterKind kind) {
logEvent("ValuedFormalParameter");
}
diff --git a/_fe_analyzer_shared/lib/src/parser/parser_impl.dart b/_fe_analyzer_shared/lib/src/parser/parser_impl.dart
index 8378c59..a2bf742 100644
--- a/_fe_analyzer_shared/lib/src/parser/parser_impl.dart
+++ b/_fe_analyzer_shared/lib/src/parser/parser_impl.dart
@@ -568,7 +568,7 @@
optional('class', next.next!)) {
macroToken = next;
next = next.next!;
- } else if (next.isIdentifier && next.lexeme == 'sealed') {
+ } else if (next.isIdentifier && optional('sealed', next)) {
sealedToken = next;
if (optional('class', next.next!) || optional('mixin', next.next!)) {
next = next.next!;
@@ -579,12 +579,12 @@
start = next;
next = next.next!.next!;
}
- } else if (next.isIdentifier && next.lexeme == 'base') {
+ } else if (next.isIdentifier && optional('base', next)) {
baseToken = next;
if (optional('class', next.next!) || optional('mixin', next.next!)) {
next = next.next!;
}
- } else if (next.isIdentifier && next.lexeme == 'interface') {
+ } else if (next.isIdentifier && optional('interface', next)) {
interfaceToken = next;
if (optional('class', next.next!) || optional('mixin', next.next!)) {
next = next.next!;
@@ -712,8 +712,6 @@
return parseTypedef(keyword);
} else if (identical(value, 'mixin')) {
if (identical(nextValue, 'class')) {
- // TODO(kallentu): Error handling for any class modifier here other
- // than base. Only base mixin classes are allowed.
return _handleModifiersForClassDeclaration(
start,
keyword.next!,
@@ -721,7 +719,7 @@
inlineToken,
sealedToken,
baseToken,
- /* interfaceToken = */ null,
+ interfaceToken,
keyword,
directiveState);
}
@@ -767,6 +765,19 @@
ModifierContext context = new ModifierContext(this);
if (mixinToken != null) {
context.parseClassModifiers(start, mixinToken);
+
+ // Mixin classes can't have any modifier other than a base modifier.
+ if (context.finalToken != null) {
+ reportRecoverableError(
+ context.finalToken!, codes.messageFinalMixinClass);
+ }
+ if (interfaceToken != null) {
+ reportRecoverableError(
+ interfaceToken, codes.messageInterfaceMixinClass);
+ }
+ if (sealedToken != null) {
+ reportRecoverableError(sealedToken, codes.messageSealedMixinClass);
+ }
} else {
context.parseClassModifiers(start, classKeyword);
}
@@ -2086,7 +2097,7 @@
listener.endFormalParameterDefaultValueExpression();
// TODO(danrubel): Consider removing the last parameter from the
// handleValuedFormalParameter event... it appears to be unused.
- listener.handleValuedFormalParameter(equal, next);
+ listener.handleValuedFormalParameter(equal, next, parameterKind);
if (parameterKind.isRequiredPositional) {
reportRecoverableError(
equal, codes.messageRequiredParameterWithDefault);
@@ -2544,7 +2555,16 @@
Token? mixinToken,
Token classKeyword) {
assert(optional('class', classKeyword));
- Token begin = abstractToken ?? classKeyword;
+ Token begin = abstractToken ??
+ macroToken ??
+ inlineToken ??
+ sealedToken ??
+ baseToken ??
+ interfaceToken ??
+ finalToken ??
+ augmentToken ??
+ mixinToken ??
+ classKeyword;
listener.beginClassOrMixinOrNamedMixinApplicationPrelude(begin);
Token name = ensureIdentifier(
classKeyword, IdentifierContext.classOrMixinOrExtensionDeclaration);
diff --git a/_fe_analyzer_shared/lib/src/scanner/token.dart b/_fe_analyzer_shared/lib/src/scanner/token.dart
index 5d8009b..b92d14f 100644
--- a/_fe_analyzer_shared/lib/src/scanner/token.dart
+++ b/_fe_analyzer_shared/lib/src/scanner/token.dart
@@ -125,218 +125,224 @@
static const Keyword AWAIT =
const Keyword(/* index = */ 84, "await", "AWAIT", KeywordStyle.pseudo);
+ static const Keyword BASE =
+ const Keyword(/* index = */ 85, "base", "BASE", KeywordStyle.pseudo);
+
static const Keyword BREAK =
- const Keyword(/* index = */ 85, "break", "BREAK", KeywordStyle.reserved);
+ const Keyword(/* index = */ 86, "break", "BREAK", KeywordStyle.reserved);
static const Keyword CASE =
- const Keyword(/* index = */ 86, "case", "CASE", KeywordStyle.reserved);
+ const Keyword(/* index = */ 87, "case", "CASE", KeywordStyle.reserved);
static const Keyword CATCH =
- const Keyword(/* index = */ 87, "catch", "CATCH", KeywordStyle.reserved);
+ const Keyword(/* index = */ 88, "catch", "CATCH", KeywordStyle.reserved);
static const Keyword CLASS = const Keyword(
- /* index = */ 88, "class", "CLASS", KeywordStyle.reserved,
+ /* index = */ 89, "class", "CLASS", KeywordStyle.reserved,
isTopLevelKeyword: true);
static const Keyword CONST = const Keyword(
- /* index = */ 89, "const", "CONST", KeywordStyle.reserved,
+ /* index = */ 90, "const", "CONST", KeywordStyle.reserved,
isModifier: true);
static const Keyword CONTINUE = const Keyword(
- /* index = */ 90, "continue", "CONTINUE", KeywordStyle.reserved);
+ /* index = */ 91, "continue", "CONTINUE", KeywordStyle.reserved);
static const Keyword COVARIANT = const Keyword(
- /* index = */ 91, "covariant", "COVARIANT", KeywordStyle.builtIn,
+ /* index = */ 92, "covariant", "COVARIANT", KeywordStyle.builtIn,
isModifier: true);
static const Keyword DEFAULT = const Keyword(
- /* index = */ 92, "default", "DEFAULT", KeywordStyle.reserved);
+ /* index = */ 93, "default", "DEFAULT", KeywordStyle.reserved);
static const Keyword DEFERRED = const Keyword(
- /* index = */ 93, "deferred", "DEFERRED", KeywordStyle.builtIn);
+ /* index = */ 94, "deferred", "DEFERRED", KeywordStyle.builtIn);
static const Keyword DO =
- const Keyword(/* index = */ 94, "do", "DO", KeywordStyle.reserved);
+ const Keyword(/* index = */ 95, "do", "DO", KeywordStyle.reserved);
static const Keyword DYNAMIC = const Keyword(
- /* index = */ 95, "dynamic", "DYNAMIC", KeywordStyle.builtIn);
+ /* index = */ 96, "dynamic", "DYNAMIC", KeywordStyle.builtIn);
static const Keyword ELSE =
- const Keyword(/* index = */ 96, "else", "ELSE", KeywordStyle.reserved);
+ const Keyword(/* index = */ 97, "else", "ELSE", KeywordStyle.reserved);
static const Keyword ENUM = const Keyword(
- /* index = */ 97, "enum", "ENUM", KeywordStyle.reserved,
+ /* index = */ 98, "enum", "ENUM", KeywordStyle.reserved,
isTopLevelKeyword: true);
static const Keyword EXPORT = const Keyword(
- /* index = */ 98, "export", "EXPORT", KeywordStyle.builtIn,
+ /* index = */ 99, "export", "EXPORT", KeywordStyle.builtIn,
isTopLevelKeyword: true);
static const Keyword EXTENDS = const Keyword(
- /* index = */ 99, "extends", "EXTENDS", KeywordStyle.reserved);
+ /* index = */ 100, "extends", "EXTENDS", KeywordStyle.reserved);
static const Keyword EXTENSION = const Keyword(
- /* index = */ 100, "extension", "EXTENSION", KeywordStyle.builtIn,
+ /* index = */ 101, "extension", "EXTENSION", KeywordStyle.builtIn,
isTopLevelKeyword: true);
static const Keyword EXTERNAL = const Keyword(
- /* index = */ 101, "external", "EXTERNAL", KeywordStyle.builtIn,
+ /* index = */ 102, "external", "EXTERNAL", KeywordStyle.builtIn,
isModifier: true);
static const Keyword FACTORY = const Keyword(
- /* index = */ 102, "factory", "FACTORY", KeywordStyle.builtIn);
+ /* index = */ 103, "factory", "FACTORY", KeywordStyle.builtIn);
static const Keyword FALSE =
- const Keyword(/* index = */ 103, "false", "FALSE", KeywordStyle.reserved);
+ const Keyword(/* index = */ 104, "false", "FALSE", KeywordStyle.reserved);
static const Keyword FINAL = const Keyword(
- /* index = */ 104, "final", "FINAL", KeywordStyle.reserved,
+ /* index = */ 105, "final", "FINAL", KeywordStyle.reserved,
isModifier: true);
static const Keyword FINALLY = const Keyword(
- /* index = */ 105, "finally", "FINALLY", KeywordStyle.reserved);
+ /* index = */ 106, "finally", "FINALLY", KeywordStyle.reserved);
static const Keyword FOR =
- const Keyword(/* index = */ 106, "for", "FOR", KeywordStyle.reserved);
+ const Keyword(/* index = */ 107, "for", "FOR", KeywordStyle.reserved);
static const Keyword FUNCTION = const Keyword(
- /* index = */ 107, "Function", "FUNCTION", KeywordStyle.builtIn);
+ /* index = */ 108, "Function", "FUNCTION", KeywordStyle.builtIn);
static const Keyword GET =
- const Keyword(/* index = */ 108, "get", "GET", KeywordStyle.builtIn);
+ const Keyword(/* index = */ 109, "get", "GET", KeywordStyle.builtIn);
static const Keyword HIDE =
- const Keyword(/* index = */ 109, "hide", "HIDE", KeywordStyle.pseudo);
+ const Keyword(/* index = */ 110, "hide", "HIDE", KeywordStyle.pseudo);
static const Keyword IF =
- const Keyword(/* index = */ 110, "if", "IF", KeywordStyle.reserved);
+ const Keyword(/* index = */ 111, "if", "IF", KeywordStyle.reserved);
static const Keyword IMPLEMENTS = const Keyword(
- /* index = */ 111, "implements", "IMPLEMENTS", KeywordStyle.builtIn);
+ /* index = */ 112, "implements", "IMPLEMENTS", KeywordStyle.builtIn);
static const Keyword IMPORT = const Keyword(
- /* index = */ 112, "import", "IMPORT", KeywordStyle.builtIn,
+ /* index = */ 113, "import", "IMPORT", KeywordStyle.builtIn,
isTopLevelKeyword: true);
static const Keyword IN =
- const Keyword(/* index = */ 113, "in", "IN", KeywordStyle.reserved);
+ const Keyword(/* index = */ 114, "in", "IN", KeywordStyle.reserved);
static const Keyword INOUT =
- const Keyword(/* index = */ 114, "inout", "INOUT", KeywordStyle.pseudo);
+ const Keyword(/* index = */ 115, "inout", "INOUT", KeywordStyle.pseudo);
static const Keyword INTERFACE = const Keyword(
- /* index = */ 115, "interface", "INTERFACE", KeywordStyle.builtIn);
+ /* index = */ 116, "interface", "INTERFACE", KeywordStyle.builtIn);
static const Keyword IS = const Keyword(
- /* index = */ 116, "is", "IS", KeywordStyle.reserved,
+ /* index = */ 117, "is", "IS", KeywordStyle.reserved,
precedence: RELATIONAL_PRECEDENCE);
static const Keyword LATE = const Keyword(
- /* index = */ 117, "late", "LATE", KeywordStyle.builtIn,
+ /* index = */ 118, "late", "LATE", KeywordStyle.builtIn,
isModifier: true);
static const Keyword LIBRARY = const Keyword(
- /* index = */ 118, "library", "LIBRARY", KeywordStyle.builtIn,
+ /* index = */ 119, "library", "LIBRARY", KeywordStyle.builtIn,
isTopLevelKeyword: true);
static const Keyword MIXIN = const Keyword(
- /* index = */ 119, "mixin", "MIXIN", KeywordStyle.builtIn,
+ /* index = */ 120, "mixin", "MIXIN", KeywordStyle.builtIn,
isTopLevelKeyword: true);
static const Keyword NATIVE =
- const Keyword(/* index = */ 120, "native", "NATIVE", KeywordStyle.pseudo);
+ const Keyword(/* index = */ 121, "native", "NATIVE", KeywordStyle.pseudo);
static const Keyword NEW =
- const Keyword(/* index = */ 121, "new", "NEW", KeywordStyle.reserved);
+ const Keyword(/* index = */ 122, "new", "NEW", KeywordStyle.reserved);
static const Keyword NULL =
- const Keyword(/* index = */ 122, "null", "NULL", KeywordStyle.reserved);
+ const Keyword(/* index = */ 123, "null", "NULL", KeywordStyle.reserved);
static const Keyword OF =
- const Keyword(/* index = */ 123, "of", "OF", KeywordStyle.pseudo);
+ const Keyword(/* index = */ 124, "of", "OF", KeywordStyle.pseudo);
static const Keyword ON =
- const Keyword(/* index = */ 124, "on", "ON", KeywordStyle.pseudo);
+ const Keyword(/* index = */ 125, "on", "ON", KeywordStyle.pseudo);
static const Keyword OPERATOR = const Keyword(
- /* index = */ 125, "operator", "OPERATOR", KeywordStyle.builtIn);
+ /* index = */ 126, "operator", "OPERATOR", KeywordStyle.builtIn);
static const Keyword OUT =
- const Keyword(/* index = */ 126, "out", "OUT", KeywordStyle.pseudo);
+ const Keyword(/* index = */ 127, "out", "OUT", KeywordStyle.pseudo);
static const Keyword PART = const Keyword(
- /* index = */ 127, "part", "PART", KeywordStyle.builtIn,
+ /* index = */ 128, "part", "PART", KeywordStyle.builtIn,
isTopLevelKeyword: true);
static const Keyword PATCH =
- const Keyword(/* index = */ 128, "patch", "PATCH", KeywordStyle.pseudo);
+ const Keyword(/* index = */ 129, "patch", "PATCH", KeywordStyle.pseudo);
static const Keyword REQUIRED = const Keyword(
- /* index = */ 129, "required", "REQUIRED", KeywordStyle.builtIn,
+ /* index = */ 130, "required", "REQUIRED", KeywordStyle.builtIn,
isModifier: true);
static const Keyword RETHROW = const Keyword(
- /* index = */ 130, "rethrow", "RETHROW", KeywordStyle.reserved);
+ /* index = */ 131, "rethrow", "RETHROW", KeywordStyle.reserved);
static const Keyword RETURN = const Keyword(
- /* index = */ 131, "return", "RETURN", KeywordStyle.reserved);
+ /* index = */ 132, "return", "RETURN", KeywordStyle.reserved);
+
+ static const Keyword SEALED =
+ const Keyword(/* index = */ 133, "sealed", "SEALED", KeywordStyle.pseudo);
static const Keyword SET =
- const Keyword(/* index = */ 132, "set", "SET", KeywordStyle.builtIn);
+ const Keyword(/* index = */ 134, "set", "SET", KeywordStyle.builtIn);
static const Keyword SHOW =
- const Keyword(/* index = */ 133, "show", "SHOW", KeywordStyle.pseudo);
+ const Keyword(/* index = */ 135, "show", "SHOW", KeywordStyle.pseudo);
static const Keyword SOURCE =
- const Keyword(/* index = */ 134, "source", "SOURCE", KeywordStyle.pseudo);
+ const Keyword(/* index = */ 136, "source", "SOURCE", KeywordStyle.pseudo);
static const Keyword STATIC = const Keyword(
- /* index = */ 135, "static", "STATIC", KeywordStyle.builtIn,
+ /* index = */ 137, "static", "STATIC", KeywordStyle.builtIn,
isModifier: true);
static const Keyword SUPER =
- const Keyword(/* index = */ 136, "super", "SUPER", KeywordStyle.reserved);
+ const Keyword(/* index = */ 138, "super", "SUPER", KeywordStyle.reserved);
static const Keyword SWITCH = const Keyword(
- /* index = */ 137, "switch", "SWITCH", KeywordStyle.reserved);
+ /* index = */ 139, "switch", "SWITCH", KeywordStyle.reserved);
static const Keyword SYNC =
- const Keyword(/* index = */ 138, "sync", "SYNC", KeywordStyle.pseudo);
+ const Keyword(/* index = */ 140, "sync", "SYNC", KeywordStyle.pseudo);
static const Keyword THIS =
- const Keyword(/* index = */ 139, "this", "THIS", KeywordStyle.reserved);
+ const Keyword(/* index = */ 141, "this", "THIS", KeywordStyle.reserved);
static const Keyword THROW =
- const Keyword(/* index = */ 140, "throw", "THROW", KeywordStyle.reserved);
+ const Keyword(/* index = */ 142, "throw", "THROW", KeywordStyle.reserved);
static const Keyword TRUE =
- const Keyword(/* index = */ 141, "true", "TRUE", KeywordStyle.reserved);
+ const Keyword(/* index = */ 143, "true", "TRUE", KeywordStyle.reserved);
static const Keyword TRY =
- const Keyword(/* index = */ 142, "try", "TRY", KeywordStyle.reserved);
+ const Keyword(/* index = */ 144, "try", "TRY", KeywordStyle.reserved);
static const Keyword TYPEDEF = const Keyword(
- /* index = */ 143, "typedef", "TYPEDEF", KeywordStyle.builtIn,
+ /* index = */ 145, "typedef", "TYPEDEF", KeywordStyle.builtIn,
isTopLevelKeyword: true);
static const Keyword VAR = const Keyword(
- /* index = */ 144, "var", "VAR", KeywordStyle.reserved,
+ /* index = */ 146, "var", "VAR", KeywordStyle.reserved,
isModifier: true);
static const Keyword VOID =
- const Keyword(/* index = */ 145, "void", "VOID", KeywordStyle.reserved);
+ const Keyword(/* index = */ 147, "void", "VOID", KeywordStyle.reserved);
static const Keyword WHEN =
- const Keyword(/* index = */ 146, "when", 'WHEN', KeywordStyle.pseudo);
+ const Keyword(/* index = */ 148, "when", 'WHEN', KeywordStyle.pseudo);
static const Keyword WHILE =
- const Keyword(/* index = */ 147, "while", "WHILE", KeywordStyle.reserved);
+ const Keyword(/* index = */ 149, "while", "WHILE", KeywordStyle.reserved);
static const Keyword WITH =
- const Keyword(/* index = */ 148, "with", "WITH", KeywordStyle.reserved);
+ const Keyword(/* index = */ 150, "with", "WITH", KeywordStyle.reserved);
static const Keyword YIELD =
- const Keyword(/* index = */ 149, "yield", "YIELD", KeywordStyle.pseudo);
+ const Keyword(/* index = */ 151, "yield", "YIELD", KeywordStyle.pseudo);
static const List<Keyword> values = const <Keyword>[
ABSTRACT,
@@ -345,6 +351,7 @@
ASYNC,
AUGMENT,
AWAIT,
+ BASE,
BREAK,
CASE,
CATCH,
@@ -392,6 +399,7 @@
REQUIRED,
RETHROW,
RETURN,
+ SEALED,
SET,
SHOW,
SOURCE,
@@ -1921,6 +1929,7 @@
Keyword.ASYNC,
Keyword.AUGMENT,
Keyword.AWAIT,
+ Keyword.BASE,
Keyword.BREAK,
Keyword.CASE,
Keyword.CATCH,
@@ -1968,6 +1977,7 @@
Keyword.REQUIRED,
Keyword.RETHROW,
Keyword.RETURN,
+ Keyword.SEALED,
Keyword.SET,
Keyword.SHOW,
Keyword.SOURCE,
diff --git a/_fe_analyzer_shared/lib/src/type_inference/type_analysis_result.dart b/_fe_analyzer_shared/lib/src/type_inference/type_analysis_result.dart
index c55dd3c..989b2a1 100644
--- a/_fe_analyzer_shared/lib/src/type_inference/type_analysis_result.dart
+++ b/_fe_analyzer_shared/lib/src/type_inference/type_analysis_result.dart
@@ -51,12 +51,6 @@
/// Indicates whether variables declared in the pattern should be `late`.
final bool isLate;
- /// The initializer being assigned to this pattern via a variable declaration
- /// statement, or `null` if this pattern does not occur in a variable
- /// declaration statement, or this pattern is not the top-level pattern in
- /// the declaration.
- final Expression? initializer;
-
/// The switch scrutinee, or `null` if this pattern does not occur in a switch
/// statement or switch expression, or this pattern is not the top-level
/// pattern.
@@ -79,7 +73,6 @@
final UnnecessaryWildcardKind? unnecessaryWildcardKind;
MatchContext({
- this.initializer,
this.irrefutableContext,
required this.isFinal,
this.isLate = false,
@@ -97,7 +90,6 @@
irrefutableContext == null
? this
: new MatchContext(
- initializer: initializer,
isFinal: isFinal,
isLate: isLate,
switchScrutinee: switchScrutinee,
@@ -111,7 +103,6 @@
MatchContext<Node, Expression, Pattern, Type, Variable> withPromotionKeys(
Map<String, int> patternVariablePromotionKeys) =>
new MatchContext(
- initializer: null,
irrefutableContext: irrefutableContext,
isFinal: isFinal,
isLate: isLate,
@@ -129,7 +120,6 @@
withUnnecessaryWildcardKind(
UnnecessaryWildcardKind? unnecessaryWildcardKind) {
return new MatchContext(
- initializer: null,
irrefutableContext: irrefutableContext,
isFinal: isFinal,
isLate: isLate,
@@ -170,8 +160,32 @@
Type resolveShorting() => type;
}
+/// Result for analyzing a switch expression in
+/// [TypeAnalyzer.analyzeSwitchExpression].
+class SwitchExpressionResult<Type extends Object, Error>
+ extends SimpleTypeAnalysisResult<Type> {
+ /// Errors for non-bool guards.
+ ///
+ /// The key is the case index of the erroneous guard.
+ ///
+ /// This is `null` if no such errors where found.
+ final Map<int, Error>? nonBooleanGuardErrors;
+
+ /// The types of the guard expressions.
+ ///
+ /// The key is the case index of the guard.
+ ///
+ /// This is `null` if no such guards where present.
+ final Map<int, Type>? guardTypes;
+
+ SwitchExpressionResult(
+ {required super.type,
+ required this.nonBooleanGuardErrors,
+ required this.guardTypes});
+}
+
/// Container for the result of running type analysis on an integer literal.
-class SwitchStatementTypeAnalysisResult<Type> {
+class SwitchStatementTypeAnalysisResult<Type extends Object, Error> {
/// Whether the switch statement had a `default` clause.
final bool hasDefault;
@@ -192,12 +206,34 @@
/// The static type of the scrutinee expression.
final Type scrutineeType;
+ /// Errors for the cases that don't complete normally.
+ ///
+ /// This is `null` if no such errors where found.
+ final Map<int, Error>? switchCaseCompletesNormallyErrors;
+
+ /// Errors for non-bool guards.
+ ///
+ /// The keys of the maps are case and head indices of the erroneous guard.
+ ///
+ /// This is `null` if no such errors where found.
+ final Map<int, Map<int, Error>>? nonBooleanGuardErrors;
+
+ /// The types of the guard expressions.
+ ///
+ /// The keys of the maps are case and head indices of the guard.
+ ///
+ /// This is `null` if no such guards where present.
+ final Map<int, Map<int, Type>>? guardTypes;
+
SwitchStatementTypeAnalysisResult({
required this.hasDefault,
required this.isExhaustive,
required this.lastCaseTerminates,
required this.requiresExhaustivenessValidation,
required this.scrutineeType,
+ required this.switchCaseCompletesNormallyErrors,
+ required this.nonBooleanGuardErrors,
+ required this.guardTypes,
});
}
@@ -212,3 +248,230 @@
/// and can be removed.
logicalAndPatternOperand,
}
+
+/// Result for analyzing an assigned variable pattern in
+/// [TypeAnalyzer.analyzeAssignedVariablePattern].
+class AssignedVariablePatternResult<Error> {
+ /// Error for when a variable was assigned multiple times within a pattern.
+ final Error? duplicateAssignmentPatternVariableError;
+
+ /// Error for when the matched value type is not assignable to the variable
+ /// type in an irrefutable context.
+ final Error? patternTypeMismatchInIrrefutableContextError;
+
+ AssignedVariablePatternResult(
+ {required this.duplicateAssignmentPatternVariableError,
+ required this.patternTypeMismatchInIrrefutableContextError});
+}
+
+/// Result for analyzing an object pattern in
+/// [TypeAnalyzer.analyzeObjectPattern].
+class ObjectPatternResult<Type extends Object, Error> {
+ /// The required type of the object pattern.
+ final Type requiredType;
+
+ /// Errors for when the same property name was used multiple times in the
+ /// object pattern.
+ ///
+ /// The key is the index of the duplicate field within the object pattern.
+ ///
+ /// This is `null` if no such properties were found.
+ final Map<int, Error>? duplicateRecordPatternFieldErrors;
+
+ /// Error for when the matched value type is not assignable to the required
+ /// type in an irrefutable context.
+ final Error? patternTypeMismatchInIrrefutableContextError;
+
+ ObjectPatternResult(
+ {required this.requiredType,
+ required this.duplicateRecordPatternFieldErrors,
+ required this.patternTypeMismatchInIrrefutableContextError});
+}
+
+/// Result for analyzing a record pattern in
+/// [TypeAnalyzer.analyzeRecordPattern].
+class RecordPatternResult<Type extends Object, Error> {
+ /// The required type of the record pattern.
+ final Type requiredType;
+
+ /// Errors for when the same property name was used multiple times in the
+ /// record pattern.
+ ///
+ /// The key is the index of the duplicate field within the record pattern.
+ ///
+ /// This is `null` if no such errors where found.
+ final Map<int, Error>? duplicateRecordPatternFieldErrors;
+
+ /// Error for when the matched value type is not assignable to the required
+ /// type in an irrefutable context.
+ final Error? patternTypeMismatchInIrrefutableContextError;
+
+ RecordPatternResult(
+ {required this.requiredType,
+ required this.duplicateRecordPatternFieldErrors,
+ required this.patternTypeMismatchInIrrefutableContextError});
+}
+
+/// Result for analyzing a list pattern in [TypeAnalyzer.analyzeListPattern].
+class ListPatternResult<Type extends Object, Error> {
+ /// The required type of the list pattern.
+ final Type requiredType;
+
+ /// Errors for when multiple rest patterns occurred within the list pattern.
+ ///
+ /// The key is the index of the pattern within the list pattern.
+ ///
+ /// This is `null` if no such errors where found.
+ final Map<int, Error>? duplicateRestPatternErrors;
+
+ /// Error for when the matched value type is not assignable to the required
+ /// type in an irrefutable context.
+ final Error? patternTypeMismatchInIrrefutableContextError;
+
+ ListPatternResult(
+ {required this.requiredType,
+ required this.duplicateRestPatternErrors,
+ required this.patternTypeMismatchInIrrefutableContextError});
+}
+
+/// Result for analyzing a map pattern in [TypeAnalyzer.analyzeMapPattern].
+class MapPatternResult<Type extends Object, Error> {
+ /// The required type of the map pattern.
+ final Type requiredType;
+
+ /// Errors for when multiple rest patterns occurred within the map pattern.
+ ///
+ /// The key is the index of the pattern within the map pattern.
+ ///
+ /// This is `null` if no such errors where found.
+ final Map<int, Error>? duplicateRestPatternErrors;
+
+ /// Error for when the matched value type is not assignable to the required
+ /// type in an irrefutable context.
+ final Error? patternTypeMismatchInIrrefutableContextError;
+
+ MapPatternResult(
+ {required this.requiredType,
+ required this.duplicateRestPatternErrors,
+ required this.patternTypeMismatchInIrrefutableContextError});
+}
+
+/// Result for analyzing a constant pattern in
+/// [TypeAnalyzer.analyzeConstantPattern].
+class ConstantPatternResult<Type extends Object, Error> {
+ /// The static type of the constant expression.
+ final Type expressionType;
+
+ /// Error for when the pattern occurred in an irrefutable context.
+ final Error? refutablePatternInIrrefutableContextError;
+
+ /// Error for when the pattern, used as a case constant expression, does not
+ /// have a valid type wrt. the switch expression type.
+ final Error? caseExpressionTypeMismatchError;
+
+ ConstantPatternResult(
+ {required this.expressionType,
+ required this.refutablePatternInIrrefutableContextError,
+ required this.caseExpressionTypeMismatchError});
+}
+
+/// Result for analyzing a declared variable pattern in
+/// [TypeAnalyzer.analyzeDeclaredVariablePattern].
+class DeclaredVariablePatternResult<Type extends Object, Error> {
+ /// The static type of the variable.
+ final Type staticType;
+
+ /// Error for when the matched value type is not assignable to the static
+ /// type in an irrefutable context.
+ final Error? patternTypeMismatchInIrrefutableContextError;
+
+ DeclaredVariablePatternResult(
+ {required this.staticType,
+ required this.patternTypeMismatchInIrrefutableContextError});
+}
+
+/// Result for analyzing a logical or pattern in
+/// [TypeAnalyzer.analyzeLogicalOrPattern].
+class LogicalOrPatternResult<Error> {
+ /// Error for when the pattern occurred in an irrefutable context.
+ final Error? refutablePatternInIrrefutableContextError;
+
+ LogicalOrPatternResult(
+ {required this.refutablePatternInIrrefutableContextError});
+}
+
+/// Result for analyzing a relational pattern in
+/// [TypeAnalyzer.analyzeRelationalPattern].
+class RelationalPatternResult<Type extends Object, Error> {
+ /// The static type of the operand.
+ final Type operandType;
+
+ /// Error for when the pattern occurred in an irrefutable context.
+ final Error? refutablePatternInIrrefutableContextError;
+
+ /// Error for when the operand type is not assignable to the parameter type
+ /// of the relational operator.
+ final Error? argumentTypeNotAssignableError;
+
+ /// Error for when the relational operator does not return a bool.
+ final Error? operatorReturnTypeNotAssignableToBoolError;
+
+ RelationalPatternResult(
+ {required this.operandType,
+ required this.refutablePatternInIrrefutableContextError,
+ required this.argumentTypeNotAssignableError,
+ required this.operatorReturnTypeNotAssignableToBoolError});
+}
+
+/// Result for analyzing a null check or null assert pattern in
+/// [TypeAnalyzer.analyzeNullCheckOrAssertPattern].
+class NullCheckOrAssertPatternResult<Error> {
+ /// Error for when the pattern occurred in an irrefutable context.
+ final Error? refutablePatternInIrrefutableContextError;
+
+ /// Error for when the matched type is known to be non-null.
+ final Error? matchedTypeIsStrictlyNonNullableError;
+
+ NullCheckOrAssertPatternResult(
+ {required this.refutablePatternInIrrefutableContextError,
+ required this.matchedTypeIsStrictlyNonNullableError});
+}
+
+/// Result for analyzing a wildcard pattern
+/// [TypeAnalyzer.analyzeWildcardPattern].
+class WildcardPatternResult<Error> {
+ /// Error for when the matched value type is not assignable to the wildcard
+ /// type in an irrefutable context.
+ final Error? patternTypeMismatchInIrrefutableContextError;
+
+ WildcardPatternResult(
+ {required this.patternTypeMismatchInIrrefutableContextError});
+}
+
+/// Result for analyzing an if-case statement or element in
+/// [TypeAnalyzer.analyzeIfCaseStatement] and
+/// [TypeAnalyzer.analyzeIfCaseElement].
+class IfCaseStatementResult<Type extends Object, Error> {
+ /// The static type of the matched expression.
+ final Type matchedExpressionType;
+
+ /// Error for when the guard has a non-bool type.
+ final Error? nonBooleanGuardError;
+
+ /// The type of the guard expression, if present.
+ final Type? guardType;
+
+ IfCaseStatementResult(
+ {required this.matchedExpressionType,
+ required this.nonBooleanGuardError,
+ required this.guardType});
+}
+
+/// Result for analyzing a pattern-for-in statement or element in
+/// [TypeAnalyzer.analyzePatternForIn].
+class PatternForInResult<Error> {
+ /// Error for when the expression is not an iterable.
+ final Error? patternForInExpressionIsNotIterableError;
+
+ PatternForInResult({required this.patternForInExpressionIsNotIterableError});
+}
diff --git a/_fe_analyzer_shared/lib/src/type_inference/type_analyzer.dart b/_fe_analyzer_shared/lib/src/type_inference/type_analyzer.dart
index f0a7fce..c236f49 100644
--- a/_fe_analyzer_shared/lib/src/type_inference/type_analyzer.dart
+++ b/_fe_analyzer_shared/lib/src/type_inference/type_analyzer.dart
@@ -33,6 +33,47 @@
});
}
+/// The kind of inconsistency identified for a variable.
+enum JoinedPatternVariableInconsistency {
+ /// No inconsistency.
+ none(0),
+
+ /// Only one branch of a logical-or pattern has the variable.
+ logicalOr(4),
+
+ /// Not every case of a shared case scope has the variable.
+ sharedCaseAbsent(3),
+
+ /// The shared case scope has a label or `default` case.
+ sharedCaseHasLabel(2),
+
+ /// The finality or type of the variable components is not the same.
+ /// This is reported for both logical-or and shared cases.
+ differentFinalityOrType(1);
+
+ final int _severity;
+
+ const JoinedPatternVariableInconsistency(this._severity);
+
+ /// Returns the most serious inconsistency for `this` or [other].
+ JoinedPatternVariableInconsistency maxWith(
+ JoinedPatternVariableInconsistency other,
+ ) {
+ return _severity > other._severity ? this : other;
+ }
+
+ /// Returns the most serious inconsistency for `this` or [others].
+ JoinedPatternVariableInconsistency maxWithAll(
+ Iterable<JoinedPatternVariableInconsistency> others,
+ ) {
+ JoinedPatternVariableInconsistency result = this;
+ for (JoinedPatternVariableInconsistency other in others) {
+ result = result.maxWith(other);
+ }
+ return result;
+ }
+}
+
/// The location where the join of a pattern variable happens.
enum JoinedPatternVariableLocation {
/// A single pattern, from `logical-or` patterns.
@@ -161,8 +202,11 @@
/// might become not consistent.
final Map<String, Variable> variables;
- SwitchStatementMemberInfo(this.heads, this.body, this.variables,
- {required this.hasLabels});
+ SwitchStatementMemberInfo(
+ {required this.heads,
+ required this.body,
+ required this.variables,
+ required this.hasLabels});
}
/// Type analysis logic to be shared between the analyzer and front end. The
@@ -238,7 +282,8 @@
Expression extends Node,
Variable extends Object,
Type extends Object,
- Pattern extends Node> {
+ Pattern extends Node,
+ Error> {
/// Returns the type `bool`.
Type get boolType;
@@ -248,8 +293,8 @@
/// Returns the type `dynamic`.
Type get dynamicType;
- TypeAnalyzerErrors<Node, Statement, Expression, Variable, Type, Pattern>?
- get errors;
+ TypeAnalyzerErrors<Node, Statement, Expression, Variable, Type, Pattern,
+ Error> get errors;
/// Returns the type used by the client in the case of errors.
Type get errorType;
@@ -277,23 +322,28 @@
/// context. [node] is the pattern itself, and [variable] is the variable
/// being referenced.
///
+ /// Returns an [AssignedVariablePatternResult] with information about reported
+ /// errors.
+ ///
/// See [dispatchPattern] for the meaning of [context].
///
/// For wildcard patterns in an assignment context,
/// [analyzeDeclaredVariablePattern] should be used instead.
///
/// Stack effect: none.
- void analyzeAssignedVariablePattern(
+ AssignedVariablePatternResult<Error> analyzeAssignedVariablePattern(
MatchContext<Node, Expression, Pattern, Type, Variable> context,
Pattern node,
Variable variable) {
+ Error? duplicateAssignmentPatternVariableError;
Map<Variable, Pattern>? assignedVariables = context.assignedVariables;
if (assignedVariables != null) {
Pattern? original = assignedVariables[variable];
if (original == null) {
assignedVariables[variable] = node;
} else {
- errors?.duplicateAssignmentPatternVariable(
+ duplicateAssignmentPatternVariableError =
+ errors.duplicateAssignmentPatternVariable(
variable: variable,
original: original,
duplicate: node,
@@ -306,17 +356,25 @@
assert(irrefutableContext != null,
'Assigned variables must only appear in irrefutable pattern contexts');
Type matchedType = flow.getMatchedValueType();
+ Error? patternTypeMismatchInIrrefutableContextError;
if (irrefutableContext != null &&
- !operations.isAssignableTo(matchedType, variableDeclaredType)) {
- errors?.patternTypeMismatchInIrrefutableContext(
- pattern: node,
- context: irrefutableContext,
- matchedType: matchedType,
- requiredType: variableDeclaredType);
+ !operations.isDynamic(matchedType) &&
+ !operations.isSubtypeOf(matchedType, variableDeclaredType)) {
+ patternTypeMismatchInIrrefutableContextError =
+ errors.patternTypeMismatchInIrrefutableContext(
+ pattern: node,
+ context: irrefutableContext,
+ matchedType: matchedType,
+ requiredType: variableDeclaredType);
}
flow.promoteForPattern(
matchedType: matchedType, knownType: variableDeclaredType);
flow.assignedVariablePattern(node, variable, matchedType);
+ return new AssignedVariablePatternResult(
+ duplicateAssignmentPatternVariableError:
+ duplicateAssignmentPatternVariableError,
+ patternTypeMismatchInIrrefutableContextError:
+ patternTypeMismatchInIrrefutableContextError);
}
/// Computes the type schema for a variable pattern appearing in an assignment
@@ -342,7 +400,7 @@
knownType: requiredType,
matchFailsIfWrongType: false);
if (matchedTypeIsSubtypeOfRequired) {
- errors?.matchedTypeIsSubtypeOfRequired(
+ errors.matchedTypeIsSubtypeOfRequired(
pattern: pattern,
matchedType: matchedValueType,
requiredType: requiredType,
@@ -371,27 +429,29 @@
///
/// See [dispatchPattern] for the meaning of [context].
///
- /// Returns the static type of [expression].
+ /// Returns a [ConstantPatternResult] with the static type of [expression]
+ /// and information about reported errors.
///
/// Stack effect: pushes (Expression).
- Type analyzeConstantPattern(
+ ConstantPatternResult<Type, Error> analyzeConstantPattern(
MatchContext<Node, Expression, Pattern, Type, Variable> context,
Node node,
Expression expression) {
// Stack: ()
- TypeAnalyzerErrors<Node, Node, Expression, Variable, Type, Pattern>?
- errors = this.errors;
Node? irrefutableContext = context.irrefutableContext;
+ Error? refutablePatternInIrrefutableContextError;
if (irrefutableContext != null) {
- errors?.refutablePatternInIrrefutableContext(
- pattern: node, context: irrefutableContext);
+ refutablePatternInIrrefutableContextError =
+ errors.refutablePatternInIrrefutableContext(
+ pattern: node, context: irrefutableContext);
}
Type matchedType = flow.getMatchedValueType();
Type expressionType = analyzeExpression(expression, matchedType);
flow.constantPattern_end(expression, expressionType,
patternsEnabled: options.patternsEnabled);
// Stack: (Expression)
- if (errors != null && !options.patternsEnabled) {
+ Error? caseExpressionTypeMismatchError;
+ if (!options.patternsEnabled) {
Expression? switchScrutinee = context.switchScrutinee;
if (switchScrutinee != null) {
bool nullSafetyEnabled = options.nullSafetyEnabled;
@@ -399,7 +459,7 @@
? operations.isSubtypeOf(expressionType, matchedType)
: operations.isAssignableTo(expressionType, matchedType);
if (!matches) {
- errors.caseExpressionTypeMismatch(
+ caseExpressionTypeMismatchError = errors.caseExpressionTypeMismatch(
caseExpression: expression,
scrutinee: switchScrutinee,
caseExpressionType: expressionType,
@@ -408,7 +468,11 @@
}
}
}
- return expressionType;
+ return new ConstantPatternResult(
+ expressionType: expressionType,
+ refutablePatternInIrrefutableContextError:
+ refutablePatternInIrrefutableContextError,
+ caseExpressionTypeMismatchError: caseExpressionTypeMismatchError);
}
/// Computes the type schema for a constant pattern.
@@ -418,7 +482,7 @@
// Constant patterns are only allowed in refutable contexts, and refutable
// contexts don't propagate a type schema into the scrutinee. So this
// code path is only reachable if the user's code contains errors.
- errors?.assertInErrorRecovery();
+ errors.assertInErrorRecovery();
return unknownType;
}
@@ -431,10 +495,11 @@
///
/// See [dispatchPattern] for the meaning of [context].
///
- /// Returns the static type of the variable (possibly inferred).
+ /// Returns a [DeclaredVariablePatternResult] with the static type of the
+ /// variable (possibly inferred) and information about reported errors.
///
/// Stack effect: none.
- Type analyzeDeclaredVariablePattern(
+ DeclaredVariablePatternResult<Type, Error> analyzeDeclaredVariablePattern(
MatchContext<Node, Expression, Pattern, Type, Variable> context,
Pattern node,
Variable variable,
@@ -445,29 +510,38 @@
Type staticType =
declaredType ?? variableTypeFromInitializerType(matchedType);
Node? irrefutableContext = context.irrefutableContext;
+ Error? patternTypeMismatchInIrrefutableContextError;
if (irrefutableContext != null &&
- !operations.isAssignableTo(matchedType, staticType)) {
- errors?.patternTypeMismatchInIrrefutableContext(
- pattern: node,
- context: irrefutableContext,
- matchedType: matchedType,
- requiredType: staticType);
+ !operations.isDynamic(matchedType) &&
+ !operations.isSubtypeOf(matchedType, staticType)) {
+ patternTypeMismatchInIrrefutableContextError =
+ errors.patternTypeMismatchInIrrefutableContext(
+ pattern: node,
+ context: irrefutableContext,
+ matchedType: matchedType,
+ requiredType: staticType);
}
flow.promoteForPattern(matchedType: matchedType, knownType: staticType);
+ // The promotion may have made the matched type even more specific than
+ // either `matchedType` or `staticType`, so fetch it again and use that
+ // in the call to `declaredVariablePattern` below.
+ matchedType = flow.getMatchedValueType();
bool isImplicitlyTyped = declaredType == null;
// TODO(paulberry): are we handling _isFinal correctly?
int promotionKey = context.patternVariablePromotionKeys[variableName] =
flow.declaredVariablePattern(
matchedType: matchedType,
staticType: staticType,
- initializerExpression: context.initializer,
isFinal: context.isFinal || isVariableFinal(variable),
isLate: context.isLate,
isImplicitlyTyped: isImplicitlyTyped);
setVariableType(variable, staticType);
(context.componentVariables[variableName] ??= []).add(variable);
flow.assignMatchedPatternVariable(variable, promotionKey);
- return staticType;
+ return new DeclaredVariablePatternResult(
+ staticType: staticType,
+ patternTypeMismatchInIrrefutableContextError:
+ patternTypeMismatchInIrrefutableContextError);
}
/// Computes the type schema for a variable pattern in a non-assignment
@@ -509,12 +583,15 @@
/// wishes to use to represent that variable. This is used to join together
/// variables that appear in different branches of logical-or patterns.
///
+ /// Returns a [IfCaseStatementResult] with the static type of [expression] and
+ /// information about reported errors.
+ ///
/// Stack effect: pushes (Expression scrutinee, Pattern, Expression guard,
/// CollectionElement ifTrue, CollectionElement ifFalse). If there is no
/// `else` clause, the representation for `ifFalse` will be pushed by
/// [handleNoCollectionElement]. If there is no guard, the representation
/// for `guard` will be pushed by [handleNoGuard].
- void analyzeIfCaseElement({
+ IfCaseStatementResult<Type, Error> analyzeIfCaseElement({
required Node node,
required Expression expression,
required Pattern pattern,
@@ -544,14 +621,21 @@
_finishJoinedPatternVariables(
variables, componentVariables, patternVariablePromotionKeys,
location: JoinedPatternVariableLocation.singlePattern);
+ Error? nonBooleanGuardError;
+ Type? guardType;
if (guard != null) {
- _checkGuardType(guard, analyzeExpression(guard, boolType));
+ guardType = analyzeExpression(guard, boolType);
+ nonBooleanGuardError = _checkGuardType(guard, guardType);
} else {
handleNoGuard(node, 0);
}
// Stack: (Expression, Pattern, Guard)
flow.ifCaseStatement_thenBegin(guard);
_analyzeIfElementCommon(node, ifTrue, ifFalse, context);
+ return new IfCaseStatementResult(
+ matchedExpressionType: initializerType,
+ nonBooleanGuardError: nonBooleanGuardError,
+ guardType: guardType);
}
/// Analyzes a statement of the form `if (expression case pattern) ifTrue` or
@@ -561,14 +645,15 @@
/// the expression, [pattern] for the pattern to match, [ifTrue] for the
/// "then" branch, and [ifFalse] for the "else" branch (if present).
///
- /// Returns the static type of [expression].
+ /// Returns a [IfCaseStatementResult] with the static type of [expression] and
+ /// information about reported errors.
///
/// Stack effect: pushes (Expression scrutinee, Pattern, Expression guard,
/// Statement ifTrue, Statement ifFalse). If there is no `else` clause, the
/// representation for `ifFalse` will be pushed by [handleNoStatement]. If
/// there is no guard, the representation for `guard` will be pushed by
/// [handleNoGuard].
- Type analyzeIfCaseStatement(
+ IfCaseStatementResult<Type, Error> analyzeIfCaseStatement(
Statement node,
Expression expression,
Pattern pattern,
@@ -603,15 +688,21 @@
handle_ifCaseStatement_afterPattern(node: node);
// Stack: (Expression, Pattern)
+ Error? nonBooleanGuardError;
+ Type? guardType;
if (guard != null) {
- _checkGuardType(guard, analyzeExpression(guard, boolType));
+ guardType = analyzeExpression(guard, boolType);
+ nonBooleanGuardError = _checkGuardType(guard, guardType);
} else {
handleNoGuard(node, 0);
}
// Stack: (Expression, Pattern, Guard)
flow.ifCaseStatement_thenBegin(guard);
_analyzeIfCommon(node, ifTrue, ifFalse);
- return initializerType;
+ return new IfCaseStatementResult(
+ matchedExpressionType: initializerType,
+ nonBooleanGuardError: nonBooleanGuardError,
+ guardType: guardType);
}
/// Analyzes a collection element of the form `if (condition) ifTrue` or
@@ -677,10 +768,13 @@
/// the list element type (if explicitly supplied), and [elements] is the
/// list of subpatterns.
///
+ /// Returns a [ListPatternResult] with the required type and information about
+ /// reported errors.
+ ///
/// See [dispatchPattern] for the meaning of [context].
///
/// Stack effect: pushes (n * Pattern) where n = elements.length.
- Type analyzeListPattern(
+ ListPatternResult<Type, Error> analyzeListPattern(
MatchContext<Node, Expression, Pattern, Type, Variable> context,
Pattern node,
{Type? elementType,
@@ -706,10 +800,12 @@
matchMayFailEvenIfCorrectType: true);
// Stack: ()
Node? previousRestPattern;
- for (Node element in elements) {
+ Map<int, Error>? duplicateRestPatternErrors;
+ for (int i = 0; i < elements.length; i++) {
+ Node element = elements[i];
if (isRestPatternElement(element)) {
if (previousRestPattern != null) {
- errors?.duplicateRestPattern(
+ (duplicateRestPatternErrors ??= {})[i] = errors.duplicateRestPattern(
mapOrListPattern: node,
original: previousRestPattern,
duplicate: element,
@@ -733,15 +829,21 @@
}
// Stack: (n * Pattern) where n = elements.length
Node? irrefutableContext = context.irrefutableContext;
+ Error? patternTypeMismatchInIrrefutableContextError;
if (irrefutableContext != null &&
!operations.isAssignableTo(matchedType, requiredType)) {
- errors?.patternTypeMismatchInIrrefutableContext(
- pattern: node,
- context: irrefutableContext,
- matchedType: matchedType,
- requiredType: requiredType);
+ patternTypeMismatchInIrrefutableContextError =
+ errors.patternTypeMismatchInIrrefutableContext(
+ pattern: node,
+ context: irrefutableContext,
+ matchedType: matchedType,
+ requiredType: requiredType);
}
- return requiredType;
+ return new ListPatternResult(
+ requiredType: requiredType,
+ duplicateRestPatternErrors: duplicateRestPatternErrors,
+ patternTypeMismatchInIrrefutableContextError:
+ patternTypeMismatchInIrrefutableContextError);
}
/// Computes the type schema for a list pattern. [elementType] is the list
@@ -825,18 +927,22 @@
/// Analyzes a logical-or pattern. [node] is the pattern itself, and [lhs]
/// and [rhs] are the left and right sides of the `||` operator.
///
+ /// Returns a [LogicalOrPatternResult] with information about reported errors.
+ ///
/// See [dispatchPattern] for the meaning of [context].
///
/// Stack effect: pushes (Pattern left, Pattern right)
- void analyzeLogicalOrPattern(
+ LogicalOrPatternResult<Error> analyzeLogicalOrPattern(
MatchContext<Node, Expression, Pattern, Type, Variable> context,
Pattern node,
Node lhs,
Node rhs) {
Node? irrefutableContext = context.irrefutableContext;
+ Error? refutablePatternInIrrefutableContextError;
if (irrefutableContext != null) {
- errors?.refutablePatternInIrrefutableContext(
- pattern: node, context: irrefutableContext);
+ refutablePatternInIrrefutableContextError =
+ errors.refutablePatternInIrrefutableContext(
+ pattern: node, context: irrefutableContext);
// Avoid cascading errors
context = context.makeRefutable();
}
@@ -892,6 +998,9 @@
// flow control branches, the normal join process will combine promotions
// accordingly.
flow.logicalOrPattern_end();
+ return new LogicalOrPatternResult(
+ refutablePatternInIrrefutableContextError:
+ refutablePatternInIrrefutableContextError);
}
/// Computes the type schema for a logical-or pattern. [lhs] and [rhs] are
@@ -902,7 +1011,7 @@
// Logical-or patterns are only allowed in refutable contexts, and
// refutable contexts don't propagate a type schema into the scrutinee.
// So this code path is only reachable if the user's code contains errors.
- errors?.assertInErrorRecovery();
+ errors.assertInErrorRecovery();
return unknownType;
}
@@ -910,10 +1019,13 @@
/// contain explicit type arguments (if specified), and [elements] is the
/// list of subpatterns.
///
+ /// Returns a [MapPatternResult] with the required type and information about
+ /// reported errors.
+ ///
/// See [dispatchPattern] for the meaning of [context].
///
/// Stack effect: pushes (n * MapPatternElement) where n = elements.length.
- Type analyzeMapPattern(
+ MapPatternResult<Type, Error> analyzeMapPattern(
MatchContext<Node, Expression, Pattern, Type, Variable> context,
Pattern node, {
required MapPatternTypeArguments<Type>? typeArguments,
@@ -955,10 +1067,12 @@
bool hasDuplicateRestPatternReported = false;
Node? previousRestPattern;
- for (Node element in elements) {
+ Map<int, Error>? duplicateRestPatternErrors;
+ for (int i = 0; i < elements.length; i++) {
+ Node element = elements[i];
if (isRestPatternElement(element)) {
if (previousRestPattern != null) {
- errors?.duplicateRestPattern(
+ (duplicateRestPatternErrors ??= {})[i] = errors.duplicateRestPattern(
mapOrListPattern: node,
original: previousRestPattern,
duplicate: element,
@@ -973,24 +1087,24 @@
Node element = elements[i];
MapPatternEntry<Expression, Pattern>? entry = getMapPatternEntry(element);
if (entry != null) {
- analyzeExpression(entry.key, keyContext);
+ Type keyType = analyzeExpression(entry.key, keyContext);
flow.pushSubpattern(valueType);
dispatchPattern(
context.withUnnecessaryWildcardKind(null),
entry.value,
);
- handleMapPatternEntry(node, element);
+ handleMapPatternEntry(node, element, keyType);
flow.popSubpattern();
} else {
assert(isRestPatternElement(element));
if (!hasDuplicateRestPatternReported) {
if (i != elements.length - 1) {
- errors?.restPatternNotLastInMap(node: node, element: element);
+ errors.restPatternNotLastInMap(node: node, element: element);
}
}
Pattern? subPattern = getRestPatternElementPattern(element);
if (subPattern != null) {
- errors?.restPatternWithSubPatternInMap(node: node, element: element);
+ errors.restPatternWithSubPatternInMap(node: node, element: element);
flow.pushSubpattern(dynamicType);
dispatchPattern(
context.withUnnecessaryWildcardKind(null),
@@ -1003,16 +1117,22 @@
}
// Stack: (n * MapPatternElement) where n = elements.length
Node? irrefutableContext = context.irrefutableContext;
+ Error? patternTypeMismatchInIrrefutableContextError;
if (irrefutableContext != null &&
!operations.isAssignableTo(matchedType, requiredType)) {
- errors?.patternTypeMismatchInIrrefutableContext(
+ patternTypeMismatchInIrrefutableContextError =
+ errors.patternTypeMismatchInIrrefutableContext(
pattern: node,
context: irrefutableContext,
matchedType: matchedType,
requiredType: requiredType,
);
}
- return requiredType;
+ return new MapPatternResult(
+ requiredType: requiredType,
+ duplicateRestPatternErrors: duplicateRestPatternErrors,
+ patternTypeMismatchInIrrefutableContextError:
+ patternTypeMismatchInIrrefutableContextError);
}
/// Computes the type schema for a map pattern. [typeArguments] contain
@@ -1053,25 +1173,32 @@
/// itself, [innerPattern] is the sub-pattern, and [isAssert] indicates
/// whether this is a null-check or a null-assert pattern.
///
+ /// Returns a [NullCheckOrAssertPatternResult] with information about
+ /// reported errors.
+ ///
/// See [dispatchPattern] for the meaning of [context].
///
/// Stack effect: pushes (Pattern innerPattern).
- void analyzeNullCheckOrAssertPattern(
+ NullCheckOrAssertPatternResult<Error> analyzeNullCheckOrAssertPattern(
MatchContext<Node, Expression, Pattern, Type, Variable> context,
Pattern node,
Pattern innerPattern,
{required bool isAssert}) {
// Stack: ()
+ Error? refutablePatternInIrrefutableContextError;
+ Error? matchedTypeIsStrictlyNonNullableError;
Node? irrefutableContext = context.irrefutableContext;
bool matchedTypeIsStrictlyNonNullable =
flow.nullCheckOrAssertPattern_begin(isAssert: isAssert);
if (irrefutableContext != null && !isAssert) {
- errors?.refutablePatternInIrrefutableContext(
- pattern: node, context: irrefutableContext);
+ refutablePatternInIrrefutableContextError =
+ errors.refutablePatternInIrrefutableContext(
+ pattern: node, context: irrefutableContext);
// Avoid cascading errors
context = context.makeRefutable();
} else if (matchedTypeIsStrictlyNonNullable) {
- errors?.matchedTypeIsStrictlyNonNullable(
+ matchedTypeIsStrictlyNonNullableError =
+ errors.matchedTypeIsStrictlyNonNullable(
pattern: node,
matchedType: flow.getMatchedValueType(),
);
@@ -1082,6 +1209,12 @@
);
// Stack: (Pattern)
flow.nullCheckOrAssertPattern_end();
+
+ return new NullCheckOrAssertPatternResult(
+ refutablePatternInIrrefutableContextError:
+ refutablePatternInIrrefutableContextError,
+ matchedTypeIsStrictlyNonNullableError:
+ matchedTypeIsStrictlyNonNullableError);
}
/// Computes the type schema for a null-check or null-assert pattern.
@@ -1097,7 +1230,7 @@
// Null-check patterns are only allowed in refutable contexts, and
// refutable contexts don't propagate a type schema into the scrutinee.
// So this code path is only reachable if the user's code contains errors.
- errors?.assertInErrorRecovery();
+ errors.assertInErrorRecovery();
return unknownType;
}
}
@@ -1107,15 +1240,19 @@
/// irrefutable contexts, but can be `null` in refutable contexts, then
/// [downwardInferObjectPatternRequiredType] is invoked to infer the type.
///
+ /// Returns a [ObjectPatternResult] with the required type and information
+ /// about reported errors.
+ ///
/// See [dispatchPattern] for the meaning of [context].
///
/// Stack effect: pushes (n * Pattern) where n = fields.length.
- Type analyzeObjectPattern(
+ ObjectPatternResult<Type, Error> analyzeObjectPattern(
MatchContext<Node, Expression, Pattern, Type, Variable> context,
Pattern node, {
required List<RecordPatternField<Node, Pattern>> fields,
}) {
- _reportDuplicateRecordPatternFields(node, fields);
+ Map<int, Error>? duplicateRecordPatternFieldErrors =
+ _reportDuplicateRecordPatternFields(node, fields);
Type matchedType = flow.getMatchedValueType();
Type requiredType = downwardInferObjectPatternRequiredType(
@@ -1133,9 +1270,11 @@
}
Node? irrefutableContext = context.irrefutableContext;
+ Error? patternTypeMismatchInIrrefutableContextError;
if (irrefutableContext != null &&
!operations.isAssignableTo(matchedType, requiredType)) {
- errors?.patternTypeMismatchInIrrefutableContext(
+ patternTypeMismatchInIrrefutableContextError =
+ errors.patternTypeMismatchInIrrefutableContext(
pattern: node,
context: irrefutableContext,
matchedType: matchedType,
@@ -1159,7 +1298,11 @@
}
// Stack: (n * Pattern) where n = fields.length
- return requiredType;
+ return new ObjectPatternResult(
+ requiredType: requiredType,
+ duplicateRecordPatternFieldErrors: duplicateRecordPatternFieldErrors,
+ patternTypeMismatchInIrrefutableContextError:
+ patternTypeMismatchInIrrefutableContextError);
}
/// Computes the type schema for an object pattern. [type] is the type
@@ -1188,7 +1331,6 @@
dispatchPattern(
new MatchContext<Node, Expression, Pattern, Type, Variable>(
isFinal: false,
- initializer: rhs,
irrefutableContext: node,
assignedVariables: <Variable, Pattern>{},
componentVariables: componentVariables,
@@ -1199,7 +1341,7 @@
if (componentVariables.isNotEmpty) {
// Declared pattern variables should never appear in a pattern assignment
// so this should never happen.
- errors?.assertInErrorRecovery();
+ errors.assertInErrorRecovery();
}
flow.patternAssignment_end();
// Stack: (Expression, Pattern)
@@ -1218,7 +1360,9 @@
/// `for (<keyword> <pattern> in <expression>) <body>`
///
/// Stack effect: pushes (Expression, Pattern).
- void analyzePatternForIn({
+ ///
+ /// Returns a [PatternForInResult] containing information on reported errors.
+ PatternForInResult<Error> analyzePatternForIn({
required Node node,
required bool hasAwait,
required Pattern pattern,
@@ -1233,6 +1377,7 @@
Type expressionType = analyzeExpression(expression, expressionTypeSchema);
// Stack: (Expression)
+ Error? patternForInExpressionIsNotIterableError;
Type? elementType = hasAwait
? operations.matchStreamType(expressionType)
: operations.matchIterableType(expressionType);
@@ -1240,7 +1385,8 @@
if (operations.isDynamic(expressionType)) {
elementType = dynamicType;
} else {
- errors?.patternForInExpressionIsNotIterable(
+ patternForInExpressionIsNotIterableError =
+ errors.patternForInExpressionIsNotIterable(
node: node,
expression: expression,
expressionType: expressionType,
@@ -1267,6 +1413,10 @@
dispatchBody();
flow.forEach_end();
flow.patternForIn_end();
+
+ return new PatternForInResult(
+ patternForInExpressionIsNotIterableError:
+ patternForInExpressionIsNotIterableError);
}
/// Analyzes a patternVariableDeclaration node of the form
@@ -1289,7 +1439,7 @@
{required bool isFinal, required bool isLate}) {
// Stack: ()
if (isLate && !isVariablePattern(pattern)) {
- errors?.patternDoesNotAllowLate(pattern: pattern);
+ errors.patternDoesNotAllowLate(pattern: pattern);
}
if (isLate) {
flow.lateInitializer_begin(node);
@@ -1308,7 +1458,6 @@
new MatchContext<Node, Expression, Pattern, Type, Variable>(
isFinal: isFinal,
isLate: isLate,
- initializer: initializer,
irrefutableContext: node,
componentVariables: componentVariables,
patternVariablePromotionKeys: patternVariablePromotionKeys,
@@ -1326,10 +1475,13 @@
/// Analyzes a record pattern. [node] is the pattern itself, and [fields]
/// is the list of subpatterns.
///
+ /// Returns a [RecordPatternResult] with the required type and information
+ /// about reported errors.
+ ///
/// See [dispatchPattern] for the meaning of [context].
///
/// Stack effect: pushes (n * Pattern) where n = fields.length.
- Type analyzeRecordPattern(
+ RecordPatternResult<Type, Error> analyzeRecordPattern(
MatchContext<Node, Expression, Pattern, Type, Variable> context,
Pattern node, {
required List<RecordPatternField<Node, Pattern>> fields,
@@ -1361,7 +1513,8 @@
}
}
- _reportDuplicateRecordPatternFields(node, fields);
+ Map<int, Error>? duplicateRecordPatternFieldErrors =
+ _reportDuplicateRecordPatternFields(node, fields);
// Build the required type.
int requiredTypePositionalCount = 0;
@@ -1406,9 +1559,11 @@
// Stack: (n * Pattern) where n = fields.length
Node? irrefutableContext = context.irrefutableContext;
+ Error? patternTypeMismatchInIrrefutableContextError;
if (irrefutableContext != null &&
!operations.isAssignableTo(matchedType, requiredType)) {
- errors?.patternTypeMismatchInIrrefutableContext(
+ patternTypeMismatchInIrrefutableContextError =
+ errors.patternTypeMismatchInIrrefutableContext(
pattern: node,
context: irrefutableContext,
matchedType: matchedType,
@@ -1422,7 +1577,11 @@
matchedType: matchedType,
knownType: demonstratedType,
matchFailsIfWrongType: false);
- return requiredType;
+ return new RecordPatternResult(
+ requiredType: requiredType,
+ duplicateRecordPatternFieldErrors: duplicateRecordPatternFieldErrors,
+ patternTypeMismatchInIrrefutableContextError:
+ patternTypeMismatchInIrrefutableContextError);
}
/// Computes the type schema for a record pattern.
@@ -1452,22 +1611,23 @@
/// This method will invoke [resolveRelationalPatternOperator] to obtain
/// information about the operator.
///
- /// Returns the type of the [operand].
+ /// Returns a [RelationalPatternResult] with the type of the [operand] and
+ /// information about reported errors.
///
/// See [dispatchPattern] for the meaning of [context].
///
/// Stack effect: pushes (Expression).
- Type analyzeRelationalPattern(
+ RelationalPatternResult<Type, Error> analyzeRelationalPattern(
MatchContext<Node, Expression, Pattern, Type, Variable> context,
Pattern node,
Expression operand) {
// Stack: ()
- TypeAnalyzerErrors<Node, Node, Expression, Variable, Type, Pattern>?
- errors = this.errors;
+ Error? refutablePatternInIrrefutableContextError;
Node? irrefutableContext = context.irrefutableContext;
if (irrefutableContext != null) {
- errors?.refutablePatternInIrrefutableContext(
- pattern: node, context: irrefutableContext);
+ refutablePatternInIrrefutableContextError =
+ errors.refutablePatternInIrrefutableContext(
+ pattern: node, context: irrefutableContext);
}
Type matchedValueType = flow.getMatchedValueType();
RelationalOperatorResolution<Type>? operator =
@@ -1492,26 +1652,34 @@
break;
}
// Stack: (Expression)
- if (errors != null && operator != null) {
+ Error? argumentTypeNotAssignableError;
+ Error? operatorReturnTypeNotAssignableToBoolError;
+ if (operator != null) {
Type argumentType =
isEquality ? operations.promoteToNonNull(operandType) : operandType;
if (!operations.isAssignableTo(argumentType, operator.parameterType)) {
- errors.argumentTypeNotAssignable(
- argument: operand,
- argumentType: argumentType,
+ argumentTypeNotAssignableError =
+ errors.relationalPatternOperandTypeNotAssignable(
+ pattern: node,
+ operandType: argumentType,
parameterType: operator.parameterType,
);
}
if (!operations.isAssignableTo(operator.returnType, boolType)) {
- errors.relationalPatternOperatorReturnTypeNotAssignableToBool(
+ operatorReturnTypeNotAssignableToBoolError =
+ errors.relationalPatternOperatorReturnTypeNotAssignableToBool(
pattern: node,
returnType: operator.returnType,
);
}
}
- // TODO(johnniwinther): This doesn't scale. We probably need to pass more
- // information, for instance whether this was an erroneous case.
- return operandType;
+ return new RelationalPatternResult(
+ operandType: operandType,
+ refutablePatternInIrrefutableContextError:
+ refutablePatternInIrrefutableContextError,
+ operatorReturnTypeNotAssignableToBoolError:
+ operatorReturnTypeNotAssignableToBoolError,
+ argumentTypeNotAssignableError: argumentTypeNotAssignableError);
}
/// Computes the type schema for a relational pattern.
@@ -1521,15 +1689,18 @@
// Relational patterns are only allowed in refutable contexts, and refutable
// contexts don't propagate a type schema into the scrutinee. So this
// code path is only reachable if the user's code contains errors.
- errors?.assertInErrorRecovery();
+ errors.assertInErrorRecovery();
return unknownType;
}
/// Analyzes an expression of the form `switch (expression) { cases }`.
///
+ /// Returns a [SwitchExpressionResult] with the static type of the switch
+ /// expression and information about reported errors.
+ ///
/// Stack effect: pushes (Expression, n * ExpressionCase), where n is the
/// number of cases.
- SimpleTypeAnalysisResult<Type> analyzeSwitchExpression(
+ SwitchExpressionResult<Type, Error> analyzeSwitchExpression(
Expression node, Expression scrutinee, int numCases, Type context) {
// Stack: ()
Type expressionType = analyzeExpression(scrutinee, unknownType);
@@ -1537,6 +1708,8 @@
handleSwitchScrutinee(expressionType);
flow.switchStatement_expressionEnd(null, scrutinee, expressionType);
Type? lubType;
+ Map<int, Error>? nonBooleanGuardErrors;
+ Map<int, Type>? guardTypes;
for (int i = 0; i < numCases; i++) {
// Stack: (Expression, i * ExpressionCase)
SwitchExpressionMemberInfo<Node, Expression, Variable> memberInfo =
@@ -1568,13 +1741,18 @@
guard = memberInfo.head.guard;
bool hasGuard = guard != null;
if (hasGuard) {
- _checkGuardType(guard, analyzeExpression(guard, boolType));
+ Type guardType = analyzeExpression(guard, boolType);
+ Error? nonBooleanGuardError = _checkGuardType(guard, guardType);
+ (guardTypes ??= {})[i] = guardType;
+ if (nonBooleanGuardError != null) {
+ (nonBooleanGuardErrors ??= {})[i] = nonBooleanGuardError;
+ }
// Stack: (Expression, i * ExpressionCase, Pattern, Expression)
} else {
handleNoGuard(node, i);
// Stack: (Expression, i * ExpressionCase, Pattern, Expression)
}
- handleCaseHead(node, caseIndex: i, subIndex: 0);
+ handleCaseHead(node, memberInfo.head, caseIndex: i, subIndex: 0);
} else {
handleDefault(node, caseIndex: i, subIndex: 0);
}
@@ -1594,18 +1772,18 @@
}
lubType ??= dynamicType;
// Stack: (Expression, numCases * ExpressionCase)
- bool isProvenExhaustive = flow.switchStatement_end(true);
- if (options.errorOnSwitchExhaustiveness && !isProvenExhaustive) {
- errors?.nonExhaustiveSwitch(node: node, scrutineeType: expressionType);
- }
- return new SimpleTypeAnalysisResult<Type>(type: lubType);
+ flow.switchStatement_end(true);
+ return new SwitchExpressionResult(
+ type: lubType,
+ nonBooleanGuardErrors: nonBooleanGuardErrors,
+ guardTypes: guardTypes);
}
/// Analyzes a statement of the form `switch (expression) { cases }`.
///
/// Stack effect: pushes (Expression, n * StatementCase), where n is the
/// number of cases after merging together cases that share a body.
- SwitchStatementTypeAnalysisResult<Type> analyzeSwitchStatement(
+ SwitchStatementTypeAnalysisResult<Type, Error> analyzeSwitchStatement(
Statement node, Expression scrutinee, final int numCases) {
// Stack: ()
Type scrutineeType = analyzeExpression(scrutinee, unknownType);
@@ -1614,6 +1792,9 @@
flow.switchStatement_expressionEnd(node, scrutinee, scrutineeType);
bool hasDefault = false;
bool lastCaseTerminates = true;
+ Map<int, Error>? switchCaseCompletesNormallyErrors;
+ Map<int, Map<int, Error>>? nonBooleanGuardErrors;
+ Map<int, Map<int, Type>>? guardTypes;
for (int caseIndex = 0; caseIndex < numCases; caseIndex++) {
// Stack: (Expression, numExecutionPaths * StatementCase)
flow.switchStatement_beginAlternatives();
@@ -1653,13 +1834,21 @@
// numHeads * CaseHead, Pattern),
guard = head.guard;
if (guard != null) {
- _checkGuardType(guard, analyzeExpression(guard, boolType));
+ Type guardType = analyzeExpression(guard, boolType);
+ Error? nonBooleanGuardError = _checkGuardType(guard, guardType);
+ ((guardTypes ??= {})[caseIndex] ??= {})[headIndex] = guardType;
+ if (nonBooleanGuardError != null) {
+ ((nonBooleanGuardErrors ??= {})[caseIndex] ??= {})[headIndex] =
+ nonBooleanGuardError;
+ }
// Stack: (Expression, numExecutionPaths * StatementCase,
// numHeads * CaseHead, Pattern, Expression),
} else {
handleNoGuard(node, caseIndex);
}
- handleCaseHead(node, caseIndex: caseIndex, subIndex: headIndex);
+ head = handleCaseHead(node, head,
+ caseIndex: caseIndex, subIndex: headIndex);
+ guard = head.guard;
} else {
hasDefault = true;
handleDefault(node, caseIndex: caseIndex, subIndex: headIndex);
@@ -1695,7 +1884,8 @@
options.nullSafetyEnabled &&
!options.patternsEnabled &&
!lastCaseTerminates) {
- errors?.switchCaseCompletesNormally(node: node, caseIndex: caseIndex);
+ (switchCaseCompletesNormallyErrors ??= {})[caseIndex] = errors
+ .switchCaseCompletesNormally(node: node, caseIndex: caseIndex);
}
handleMergedStatementCase(node,
caseIndex: caseIndex, isTerminating: lastCaseTerminates);
@@ -1714,18 +1904,16 @@
isExhaustive = isLegacySwitchExhaustive(node, scrutineeType);
requiresExhaustivenessValidation = false;
}
- bool isProvenExhaustive = flow.switchStatement_end(isExhaustive);
- if (options.errorOnSwitchExhaustiveness &&
- requiresExhaustivenessValidation &&
- !isProvenExhaustive) {
- errors?.nonExhaustiveSwitch(node: node, scrutineeType: scrutineeType);
- }
- return new SwitchStatementTypeAnalysisResult<Type>(
+ flow.switchStatement_end(isExhaustive);
+ return new SwitchStatementTypeAnalysisResult(
hasDefault: hasDefault,
isExhaustive: isExhaustive,
lastCaseTerminates: lastCaseTerminates,
requiresExhaustivenessValidation: requiresExhaustivenessValidation,
scrutineeType: scrutineeType,
+ switchCaseCompletesNormallyErrors: switchCaseCompletesNormallyErrors,
+ nonBooleanGuardErrors: nonBooleanGuardErrors,
+ guardTypes: guardTypes,
);
}
@@ -1751,19 +1939,23 @@
/// Analyzes a wildcard pattern. [node] is the pattern.
///
+ /// Returns a [WildcardPattern] with information about reported errors.
+ ///
/// See [dispatchPattern] for the meaning of [context].
///
/// Stack effect: none.
- void analyzeWildcardPattern({
+ WildcardPatternResult<Error> analyzeWildcardPattern({
required MatchContext<Node, Expression, Pattern, Type, Variable> context,
required Pattern node,
required Type? declaredType,
}) {
Type matchedType = flow.getMatchedValueType();
Node? irrefutableContext = context.irrefutableContext;
+ Error? patternTypeMismatchInIrrefutableContextError;
if (irrefutableContext != null && declaredType != null) {
if (!operations.isAssignableTo(matchedType, declaredType)) {
- errors?.patternTypeMismatchInIrrefutableContext(
+ patternTypeMismatchInIrrefutableContextError =
+ errors.patternTypeMismatchInIrrefutableContext(
pattern: node,
context: irrefutableContext,
matchedType: matchedType,
@@ -1783,11 +1975,14 @@
UnnecessaryWildcardKind? unnecessaryWildcardKind =
context.unnecessaryWildcardKind;
if (isAlwaysMatching && unnecessaryWildcardKind != null) {
- errors?.unnecessaryWildcardPattern(
+ errors.unnecessaryWildcardPattern(
pattern: node,
kind: unnecessaryWildcardKind,
);
}
+ return new WildcardPatternResult(
+ patternTypeMismatchInIrrefutableContextError:
+ patternTypeMismatchInIrrefutableContextError);
}
/// Computes the type schema for a wildcard pattern. [declaredType] is the
@@ -1866,7 +2061,7 @@
void finishJoinedPatternVariable(
Variable variable, {
required JoinedPatternVariableLocation location,
- required bool isConsistent,
+ required JoinedPatternVariableInconsistency inconsistency,
required bool isFinal,
required Type type,
});
@@ -1938,11 +2133,15 @@
/// Called after visiting a single `case` clause, consisting of a pattern and
/// an optional guard.
///
- /// [node] is the enclosing switch statement or switch expression and
+ /// [node] is the enclosing switch statement or switch expression,
+ /// [head] is the head to be handled, and
/// [caseIndex] is the index of the `case` clause.
///
+ /// Returns the updated case head.
+ ///
/// Stack effect: pops (Pattern, Expression) and pushes (CaseHead).
- void handleCaseHead(Node node,
+ CaseHeadOrDefaultInfo<Node, Expression, Variable> handleCaseHead(
+ Node node, CaseHeadOrDefaultInfo<Node, Expression, Variable> head,
{required int caseIndex, required int subIndex});
/// Called after visiting a `default` clause.
@@ -1966,7 +2165,8 @@
/// Called after visiting an entry element in a map pattern.
///
/// Stack effect: pushes (MapPatternElement).
- void handleMapPatternEntry(Pattern container, Node entryElement);
+ void handleMapPatternEntry(
+ Pattern container, Node entryElement, Type keyType);
/// Called after visiting a rest element in a map pattern.
///
@@ -2142,14 +2342,15 @@
// Stack: (CollectionElement ifTrue, CollectionElement ifFalse)
}
- void _checkGuardType(Expression expression, Type type) {
+ Error? _checkGuardType(Expression expression, Type type) {
// TODO(paulberry): harmonize this with analyzer's checkForNonBoolExpression
// TODO(paulberry): spec says the type must be `bool` or `dynamic`. This
// logic permits `T extends bool`, `T promoted to bool`, or `Never`. What
// do we want?
if (!operations.isAssignableTo(type, boolType)) {
- errors?.nonBooleanCondition(node: expression);
+ return errors.nonBooleanCondition(node: expression);
}
+ return null;
}
void _finishJoinedPatternVariables(
@@ -2200,7 +2401,7 @@
if (inconsistencyFound &&
location == JoinedPatternVariableLocation.singlePattern &&
variable != null) {
- errors?.inconsistentJoinedPatternVariable(
+ errors.inconsistentJoinedPatternVariable(
variable: variable, component: component);
}
}
@@ -2209,8 +2410,10 @@
if (!isIdenticalToComponent) {
finishJoinedPatternVariable(variable,
location: location,
- isConsistent:
- typeIfConsistent != null && isFinalIfConsistent != null,
+ inconsistency: typeIfConsistent != null &&
+ isFinalIfConsistent != null
+ ? JoinedPatternVariableInconsistency.none
+ : JoinedPatternVariableInconsistency.differentFinalityOrType,
isFinal: isFinalIfConsistent ?? false,
type: typeIfConsistent ?? errorType);
flow.assignMatchedPatternVariable(variable, promotionKey);
@@ -2263,17 +2466,17 @@
}
/// Reports errors for duplicate named record fields.
- void _reportDuplicateRecordPatternFields(
- Pattern pattern,
- List<RecordPatternField<Node, Pattern>> fields,
- ) {
+ Map<int, Error>? _reportDuplicateRecordPatternFields(
+ Pattern pattern, List<RecordPatternField<Node, Pattern>> fields) {
+ Map<int, Error>? errorResults;
Map<String, RecordPatternField<Node, Pattern>> nameToField = {};
- for (RecordPatternField<Node, Pattern> field in fields) {
+ for (int i = 0; i < fields.length; i++) {
+ RecordPatternField<Node, Pattern> field = fields[i];
String? name = field.name;
if (name != null) {
RecordPatternField<Node, Pattern>? original = nameToField[name];
if (original != null) {
- errors?.duplicateRecordPatternField(
+ (errorResults ??= {})[i] = errors.duplicateRecordPatternField(
objectOrRecordPattern: pattern,
name: name,
original: original,
@@ -2284,6 +2487,7 @@
}
}
}
+ return errorResults;
}
bool _structurallyEqualAfterNormTypes(Type type1, Type type2) {
@@ -2301,18 +2505,11 @@
Expression extends Node,
Variable extends Object,
Type extends Object,
- Pattern extends Node> implements TypeAnalyzerErrorsBase {
- /// Called if [argument] has type [argumentType], which is not assignable
- /// to [parameterType].
- void argumentTypeNotAssignable({
- required Expression argument,
- required Type argumentType,
- required Type parameterType,
- });
-
+ Pattern extends Node,
+ Error> implements TypeAnalyzerErrorsBase {
/// Called if pattern support is disabled and a case constant's static type
/// doesn't properly match the scrutinee's static type.
- void caseExpressionTypeMismatch(
+ Error caseExpressionTypeMismatch(
{required Expression scrutinee,
required Expression caseExpression,
required Type scrutineeType,
@@ -2320,14 +2517,16 @@
required bool nullSafetyEnabled});
/// Called for variable that is assigned more than once.
- void duplicateAssignmentPatternVariable({
+ ///
+ /// Returns an error object that is passed on the the caller.
+ Error duplicateAssignmentPatternVariable({
required Variable variable,
required Pattern original,
required Pattern duplicate,
});
/// Called for a pair of named fields have the same name.
- void duplicateRecordPatternField({
+ Error duplicateRecordPatternField({
required Pattern objectOrRecordPattern,
required String name,
required RecordPatternField<Node, Pattern> original,
@@ -2335,7 +2534,7 @@
});
/// Called for a duplicate rest pattern found in a list or map pattern.
- void duplicateRestPattern({
+ Error duplicateRestPattern({
required Pattern mapOrListPattern,
required Node original,
required Node duplicate,
@@ -2351,7 +2550,7 @@
/// Called when a null-assert or null-check pattern is used with the matched
/// type that is strictly non-nullable, so the null check is not necessary.
- void matchedTypeIsStrictlyNonNullable({
+ Error matchedTypeIsStrictlyNonNullable({
required Pattern pattern,
required Type matchedType,
});
@@ -2365,16 +2564,7 @@
});
/// Called if the static type of a condition is not assignable to `bool`.
- void nonBooleanCondition({required Expression node});
-
- /// Called if [TypeAnalyzerOptions.errorOnSwitchExhaustiveness] is `true`, and
- /// a switch that is required to be exhaustive cannot be proven by flow
- /// analysis to be exhaustive.
- ///
- /// [node] is the offending switch expression or switch statement, and
- /// [scrutineeType] is the static type of the switch statement's scrutinee
- /// expression.
- void nonExhaustiveSwitch({required Node node, required Type scrutineeType});
+ Error nonBooleanCondition({required Expression node});
/// Called if a pattern is illegally used in a variable declaration statement
/// that is marked `late`, and that pattern is not allowed in such a
@@ -2388,7 +2578,7 @@
/// that should be an `Iterable` (or dynamic) is actually not.
///
/// [expressionType] is the actual type of the [expression].
- void patternForInExpressionIsNotIterable({
+ Error patternForInExpressionIsNotIterable({
required Node node,
required Expression expression,
required Type expressionType,
@@ -2401,7 +2591,7 @@
/// the containing AST node that established an irrefutable context,
/// [matchedType] is the matched type, and [requiredType] is the required
/// type.
- void patternTypeMismatchInIrrefutableContext(
+ Error patternTypeMismatchInIrrefutableContext(
{required Pattern pattern,
required Node context,
required Type matchedType,
@@ -2413,12 +2603,20 @@
/// containing AST node that established an irrefutable context.
///
/// TODO(paulberry): move this error reporting to the parser.
- void refutablePatternInIrrefutableContext(
+ Error refutablePatternInIrrefutableContext(
{required Node pattern, required Node context});
+ /// Called if the operand of the [pattern] has the type [operandType], which
+ /// is not assignable to [parameterType] of the invoked relational operator.
+ Error relationalPatternOperandTypeNotAssignable({
+ required Pattern pattern,
+ required Type operandType,
+ required Type parameterType,
+ });
+
/// Called if the [returnType] of the invoked relational operator is not
/// assignable to `bool`.
- void relationalPatternOperatorReturnTypeNotAssignableToBool({
+ Error relationalPatternOperatorReturnTypeNotAssignableToBool({
required Pattern pattern,
required Type returnType,
});
@@ -2440,7 +2638,7 @@
///
/// [node] is the AST node of the switch statement. [caseIndex] is the index
/// of the merged case with the erroneous case body.
- void switchCaseCompletesNormally(
+ Error switchCaseCompletesNormally(
{required Statement node, required int caseIndex});
/// Called when a wildcard pattern appears in the context where it is not
@@ -2473,19 +2671,6 @@
final bool patternsEnabled;
- /// If `true`, the type analyzer should generate errors if it encounters a
- /// switch that is required to be exhaustive, but cannot be proven to be
- /// exhaustive by flow analysis.
- ///
- /// This option is intended as a temporary workaround if we want to ship an
- /// early beta of the "patterns" feature before exhaustiveness checking is
- /// sufficiently ready.
- ///
- /// TODO(paulberry): remove this option when it is no longer needed.
- final bool errorOnSwitchExhaustiveness;
-
TypeAnalyzerOptions(
- {required this.nullSafetyEnabled,
- required this.patternsEnabled,
- this.errorOnSwitchExhaustiveness = false});
+ {required this.nullSafetyEnabled, required this.patternsEnabled});
}
diff --git a/_fe_analyzer_shared/lib/src/type_inference/variable_bindings.dart b/_fe_analyzer_shared/lib/src/type_inference/variable_bindings.dart
index 1039b1b..3c122b5 100644
--- a/_fe_analyzer_shared/lib/src/type_inference/variable_bindings.dart
+++ b/_fe_analyzer_shared/lib/src/type_inference/variable_bindings.dart
@@ -90,7 +90,7 @@
Variable joinPatternVariables({
required Object key,
required List<Variable> components,
- required bool isConsistent,
+ required JoinedPatternVariableInconsistency inconsistency,
});
/// Updates the binder after visiting a logical-or pattern, joins variables
@@ -109,7 +109,7 @@
joinPatternVariables(
key: node,
components: [leftVariable, rightVariable],
- isConsistent: true,
+ inconsistency: JoinedPatternVariableInconsistency.none,
),
);
} else {
@@ -124,7 +124,7 @@
joinPatternVariables(
key: node,
components: [leftVariable],
- isConsistent: false,
+ inconsistency: JoinedPatternVariableInconsistency.logicalOr,
),
);
}
@@ -143,7 +143,7 @@
joinPatternVariables(
key: node,
components: [rightVariable],
- isConsistent: false,
+ inconsistency: JoinedPatternVariableInconsistency.logicalOr,
),
);
}
@@ -165,6 +165,7 @@
void switchStatementSharedCaseScopeEmpty(Object key) {
_SharedCaseScope<Variable> sharedScope = _sharedCaseScopes.last;
assert(sharedScope.key == key);
+ sharedScope.hasLabel = true;
sharedScope.addAll({});
}
@@ -183,13 +184,17 @@
in sharedScope.variables.entries) {
_SharedCaseScopeVariable<Variable> sharedVariable = entry.value;
List<Variable> variables = sharedVariable.variables;
- if (sharedVariable.isConsistent && variables.length == 1) {
+ if (sharedVariable.allCases && variables.length == 1) {
result[entry.key] = variables[0];
} else {
result[entry.key] = joinPatternVariables(
key: key,
components: variables,
- isConsistent: sharedVariable.isConsistent,
+ inconsistency: sharedVariable.allCases
+ ? JoinedPatternVariableInconsistency.none
+ : sharedScope.hasLabel
+ ? JoinedPatternVariableInconsistency.sharedCaseHasLabel
+ : JoinedPatternVariableInconsistency.sharedCaseAbsent,
);
}
}
@@ -230,6 +235,7 @@
class _SharedCaseScope<Variable extends Object> {
final Object key;
bool isEmpty = true;
+ bool hasLabel = false;
Map<String, _SharedCaseScopeVariable<Variable>> variables = {};
_SharedCaseScope(this.key);
@@ -253,7 +259,7 @@
if (newVariable != null) {
variable.variables.add(newVariable);
} else {
- variable.isConsistent = false;
+ variable.allCases = false;
}
}
for (MapEntry<String, Variable> newEntry in newVariables.entries) {
@@ -261,7 +267,7 @@
Variable newVariable = newEntry.value;
if (!variables.containsKey(name)) {
_getVariable(name)
- ..isConsistent = false
+ ..allCases = false
..variables.add(newVariable);
}
}
@@ -274,6 +280,6 @@
}
class _SharedCaseScopeVariable<Variable extends Object> {
- bool isConsistent = true;
+ bool allCases = true;
final List<Variable> variables = [];
}
diff --git a/_fe_analyzer_shared/pubspec.yaml b/_fe_analyzer_shared/pubspec.yaml
index cf159cb..66f18b0 100644
--- a/_fe_analyzer_shared/pubspec.yaml
+++ b/_fe_analyzer_shared/pubspec.yaml
@@ -1,5 +1,5 @@
name: _fe_analyzer_shared
-version: 54.0.0
+version: 58.0.0
description: Logic that is shared between the front_end and analyzer packages.
repository: https://github.com/dart-lang/sdk/tree/main/pkg/_fe_analyzer_shared
diff --git a/analyzer/BUILD.gn b/analyzer/BUILD.gn
index 5da73bc..cb801f4 100644
--- a/analyzer/BUILD.gn
+++ b/analyzer/BUILD.gn
@@ -1,11 +1,11 @@
-# This file is generated by package_importer.py for analyzer-5.6.0
+# This file is generated by package_importer.py for analyzer-5.10.0
import("//build/dart/dart_library.gni")
dart_library("analyzer") {
package_name = "analyzer"
- language_version = "2.17"
+ language_version = "2.19"
disable_analysis = true
@@ -153,6 +153,7 @@
"src/dart/element/replacement_visitor.dart",
"src/dart/element/runtime_type_equality.dart",
"src/dart/element/scope.dart",
+ "src/dart/element/since_sdk_version.dart",
"src/dart/element/subtype.dart",
"src/dart/element/top_merge.dart",
"src/dart/element/type.dart",
@@ -226,6 +227,7 @@
"src/error.dart",
"src/error/analyzer_error_code.dart",
"src/error/assignment_verifier.dart",
+ "src/error/base_or_final_type_verifier.dart",
"src/error/best_practices_verifier.dart",
"src/error/bool_expression_verifier.dart",
"src/error/codes.dart",
@@ -421,6 +423,7 @@
"src/utilities/extensions/stream.dart",
"src/utilities/extensions/string.dart",
"src/utilities/legacy.dart",
+ "src/utilities/since_sdk_cache.dart",
"src/utilities/uri_cache.dart",
"src/workspace/basic.dart",
"src/workspace/blaze.dart",
diff --git a/analyzer/CHANGELOG.md b/analyzer/CHANGELOG.md
index 7697be2..6581517 100644
--- a/analyzer/CHANGELOG.md
+++ b/analyzer/CHANGELOG.md
@@ -1,3 +1,22 @@
+## 5.10.0
+* Added `DartType.isDartCoreType`.
+
+## 5.9.0
+* Deprecated `FunctionBody.isPotentiallyMutatedInClosure`, not used by clients.
+* Fix for `FunctionBody.isPotentiallyMutatedInScope` and pattern assignment.
+
+## 5.8.0
+* Deprecated `DartType.isVoid`, use `is VoidType` instead.
+* `records`, `patterns`, and `class-modifiers` features enabled by default.
+
+## 5.7.1
+* Require SDK `>=2.19.0 <3.0.0` to use `PathNotFoundException` from `dart:io`.
+
+## 5.7.0
+* Work on `class-modifiers` feature.
+* Work on `patterns` feature.
+* Support for primitive equality.
+
## 5.6.0
* Fixes for patterns parsing.
* Implemented `DartPattern.precedence`.
diff --git a/analyzer/TRIAGE.md b/analyzer/TRIAGE.md
index 8b039c1..83bb51b 100644
--- a/analyzer/TRIAGE.md
+++ b/analyzer/TRIAGE.md
@@ -58,10 +58,10 @@
### P2
-* Incorrect analysis errors or warnings, on edge cases with simple workaround
+* Incorrect analysis errors, on edge cases with simple workaround
* EXAMPLE: Disabling the error or warning 'fixes' the issue and unblocks
users.
-* Incorrect analysis infos/hints, on edge cases
+* Incorrect analysis warnings, on edge cases
* Incorrect resolution of symbols or libraries, edge cases only with workarounds
* Incorrect data from analyzer API, edge cases without workaround
* Automation resulting in incorrect code, edge cases
@@ -75,8 +75,8 @@
* Uncaught exceptions caught by a fuzzer, but believed to be theoretical
situations only
-* Incorrect analysis errors or warnings, theoretical
-* Incorrect analysis infos/hints, on edge cases with workaround
+* Incorrect analysis errors, theoretical
+* Incorrect analysis warnings, on edge cases with workaround
* Incorrect resolution of symbols or libraries, theoretical
* Incorrect data from analyzer API, edge case with workaround available
* Performance regression impacting edge cases with workaround or without
@@ -89,7 +89,7 @@
### P4
-* Incorrect analysis infos/hints, theoretical
+* Incorrect analysis warnings, theoretical
* Incorrect data from analyzer API, theoretical
* Theoretical performance problems
* An enhancement that may have some evidence that it isn't a good idea to
@@ -122,8 +122,7 @@
* "corrupted code" - Modification of source code in such a way that it is
more than just a bit wrong or having some symbols that don't exist, but is
not valid Dart and would be painful to manually correct.
-* "diagnostic" - An error, warning, hint, or lint generated by the analyzer
- or linter.
+* "diagnostic" - An error, warning, or lint generated by the analyzer.
* "incorrect code" - Modification of code in a way that is known to be wrong,
but would be trivial to figure out how to fix for the human using the tool.
* "key users" - Flutter, Pub, Fuchsia, Dart, Google/1P
diff --git a/analyzer/doc/implementation/pub_diagnostics.md b/analyzer/doc/implementation/pub_diagnostics.md
new file mode 100644
index 0000000..26d1083
--- /dev/null
+++ b/analyzer/doc/implementation/pub_diagnostics.md
@@ -0,0 +1,53 @@
+# Adding a new pubspec diagnostic
+
+This document describes the process of adding a new (non-lint) pubspec
+diagnostic to the analyzer.
+
+## Background
+
+Analyzer parses pubspecs and sends change notifications to a validator that can
+be used to produce diagnostics in pubspec.yaml files. Taking advantage of this
+we can provide rich dynamic feedback to authors as they type.
+
+## Recipe
+
+The basic recipe for implementing a new pubspec diagnostic is as follows:
+
+1. Introduce a new `PubspecWarningCode` code to `messages.yaml`.
+2. Re-generate Dart error code files (run `generate_files`).
+3. Add corresponding tests to a new test library in
+ `test/src/pubspec/diagnostics`.
+4. Implement analysis in a new validator in `lib/src/pubspec/validators` and
+ invoke it from PubspecValidator (or enhance an existing one).
+
+Once implemented, you’ll want to look for ecosystem breakages. Useful bots to
+watch:
+
+* [analyzer-analysis-server-linux-try][]
+ analyzes SDK packages and is a great early warning system
+* [flutter-analyze-try][] and
+* [flutter-engine-linux-try][]
+ will tell you if your change will block an SDK roll into Flutter
+
+[analyzer-analysis-server-linux-try](https://ci.chromium.org/p/dart/builders/ci.sandbox/analyzer-analysis-server-linux)
+[flutter-analyze-try](https://ci.chromium.org/p/dart/builders/ci.sandbox/flutter-analyze)
+[flutter-engine-linux-try](https://ci.chromium.org/p/dart/builders/ci.sandbox/flutter-engine-linux)
+
+You’ll need to clean up these downstream breakages before you can land yours.
+
+In the case of SDK breakages, you can fix them in your initial PR. Flutter and
+Flutter Engine breakages should be handled in PRs to their respective Flutter
+repos.
+
+## Example: Deprecated Fields
+
+The introduction of diagnostics for deprecated fields (corresponding to the
+existing [pub client check][]) demonstrates a lot of these ideas and serves as a
+good jumping off point for future diagnostics.
+
+[pub client check](https://github.com/dart-lang/pub/blob/ab41ef0aaef7a20f759c6147aa8121a1396ee589/lib/src/validator/deprecated_fields.dart#L18-L35)
+
+1. The initial PR: https://dart-review.googlesource.com/c/sdk/+/204420 (notice
+ the breakage to analyzer_plugin and telemetry that needed fixing).
+2. Flutter repo fixes: https://github.com/flutter/flutter/pull/84997 and
+ https://github.com/flutter/flutter/pull/85036.
diff --git a/analyzer/example/ddd01.dart b/analyzer/example/ddd01.dart
index 8130a93..4d37453 100644
--- a/analyzer/example/ddd01.dart
+++ b/analyzer/example/ddd01.dart
@@ -1,14 +1,18 @@
-import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/src/dart/analysis/analysis_context_collection.dart';
void main() async {
- var path = '/Users/scheglov/dart/issue50962/br_table.0.dart';
- var collection = AnalysisContextCollectionImpl(includedPaths: [
- path,
+ final collection = AnalysisContextCollectionImpl(includedPaths: [
+ '/Users/scheglov/dart/test',
]);
- var analysisContext = collection.contextFor(path);
- var unitResult = await analysisContext.currentSession.getResolvedUnit(path);
- unitResult as ResolvedUnitResult;
- // await Future<void>.delayed(const Duration(days: 1));
+ for (final analysisContext in collection.contexts) {
+ // print(analysisContext.contextRoot.root.path);
+ final analysisSession = analysisContext.currentSession;
+ for (final path in analysisContext.contextRoot.analyzedFiles()) {
+ if (path.endsWith('.dart')) {
+ print(' $path');
+ await analysisSession.getResolvedUnit(path);
+ }
+ }
+ }
}
diff --git a/analyzer/example/ddd03.dart b/analyzer/example/ddd03.dart
index f8b3c89..eb77cca 100644
--- a/analyzer/example/ddd03.dart
+++ b/analyzer/example/ddd03.dart
@@ -26,8 +26,8 @@
includedPaths: [
path,
],
- packagesFile:
- '/Users/scheglov/dart/flutter_plugins/packages/camera/camera/.dart_tool/package_config.json',
+ // packagesFile:
+ // '/Users/scheglov/dart/flutter_plugins/packages/camera/camera/.dart_tool/package_config.json',
);
// print('[Analysis contexts: ${collection.contexts.length}]');
diff --git a/analyzer/example/ddd03_2.dart b/analyzer/example/ddd03_2.dart
new file mode 100644
index 0000000..5706860
--- /dev/null
+++ b/analyzer/example/ddd03_2.dart
@@ -0,0 +1,161 @@
+// import 'dart:convert';
+// import 'dart:io';
+//
+// import 'package:analyzer/file_system/physical_file_system.dart';
+// import 'package:analyzer/src/dart/analysis/analysis_context_collection.dart';
+// import 'package:analyzer/src/dart/analysis/byte_store.dart';
+// import 'package:analyzer/src/dart/analysis/file_content_cache.dart';
+// import 'package:analyzer/src/dart/analysis/unlinked_unit_store.dart';
+// import 'package:vm_service/vm_service.dart';
+//
+// import 'heap/analysis.dart';
+// import 'heap/format.dart';
+// import 'heap/load.dart';
+//
+// void main() async {
+// var path = '/Users/scheglov/dart/flutter_plugins/packages/camera';
+// // var path = '/Users/scheglov/dart/flutter_plugins/packages';
+//
+// while (true) {
+// var resourceProvider = PhysicalResourceProvider.INSTANCE;
+// var fileContentCache = FileContentCache(resourceProvider);
+// var unlinkedUnitStore = UnlinkedUnitStoreImpl();
+//
+// var collection = AnalysisContextCollectionImpl(
+// byteStore: MemoryByteStore(),
+// resourceProvider: resourceProvider,
+// fileContentCache: fileContentCache,
+// unlinkedUnitStore: unlinkedUnitStore,
+// sdkPath: '/Users/scheglov/Applications/dart-sdk',
+// // performanceLog: PerformanceLog(stdout),
+// includedPaths: [
+// path,
+// ],
+// // packagesFile:
+// // '/Users/scheglov/dart/flutter_plugins/packages/camera/camera/.dart_tool/package_config.json',
+// );
+//
+// // print('[Analysis contexts: ${collection.contexts.length}]');
+//
+// var timer = Stopwatch()..start();
+// for (var analysisContext in collection.contexts) {
+// // print(analysisContext.contextRoot.root.path);
+// for (var filePath in analysisContext.contextRoot.analyzedFiles()) {
+// if (filePath.endsWith('.dart')) {
+// // print(' $filePath');
+// var analysisSession = analysisContext.currentSession;
+// await analysisSession.getResolvedUnit(filePath);
+// }
+// }
+// }
+// timer.stop();
+// print('[time: ${timer.elapsedMilliseconds} ms]');
+//
+// {
+// var timer = Stopwatch()..start();
+// var chunks = await loadFromUri(Uri.parse('http://127.0.0.1:5000'));
+// // final length = chunks
+// // .map((e) => e.lengthInBytes)
+// // .fold<int>(0, (prev, e) => prev + e);
+// // print(
+// // ' [${timer.elapsedMilliseconds} ms] '
+// // 'Downloaded heap snapshot, ${length / 1024 / 1024} MB.',
+// // );
+//
+// final graph = HeapSnapshotGraph.fromChunks(chunks);
+// print(' [${timer.elapsedMilliseconds} ms] Created HeapSnapshotGraph.');
+// print(' externalSize: ${graph.externalSize}');
+// print(' shallowSize: ${graph.shallowSize}');
+// print(' Objects: ${graph.objects.length}');
+//
+// final analysis = Analysis(graph);
+// print(' [${timer.elapsedMilliseconds} ms] Created Analysis.');
+//
+// {
+// print('All objects.');
+// final objects = analysis.reachableObjects;
+// final stats = analysis.generateObjectStats(objects);
+// print(formatHeapStats(stats, maxLines: 20));
+// print('');
+// }
+//
+// {
+// print('ElementImpl(s)');
+// var fileStateList = analysis.filter(
+// analysis.reachableObjects,
+// (object) {
+// return object.klass.name.endsWith('ElementImpl');
+// },
+// );
+// analysis.printObjectStats(fileStateList);
+// print('');
+// // final allObjects = analysis.transitiveGraph(fileStateList);
+// // analysis.printObjectStats(allObjects);
+// // print('');
+// }
+//
+// {
+// print('Version(s)');
+// var objectList = analysis.filter(
+// analysis.reachableObjects,
+// (object) {
+// return object.klass.name == 'Version';
+// },
+// );
+// analysis.printObjectStats(objectList);
+// print('');
+// // final allObjects = analysis.transitiveGraph(fileStateList);
+// // analysis.printObjectStats(allObjects);
+// // print('');
+//
+// const maxEntries = 10;
+// final paths = analysis.retainingPathsOf(objectList, 10);
+// for (int i = 0; i < paths.length; ++i) {
+// if (maxEntries != -1 && i >= maxEntries) break;
+// final path = paths[i];
+// print('There are ${path.count} retaining paths of');
+// print(formatRetainingPath(analysis.graph, paths[i]));
+// print('');
+// }
+// }
+// }
+// }
+//
+// // var analysisContext = collection.contextFor(path);
+// // var unitResult = await analysisContext.currentSession.getResolvedUnit(path);
+// // unitResult as ResolvedUnitResult;
+//
+// // await Future<void>.delayed(const Duration(days: 1));
+// }
+//
+// extension on Analysis {
+// void printObjectStats(IntSet objectIds) {
+// final stats = generateObjectStats(objectIds);
+// print(formatHeapStats(stats, maxLines: 20));
+// print('');
+// }
+//
+// void printRetainers(
+// IntSet objectIds, {
+// int maxEntries = 3,
+// }) {
+// final paths = retainingPathsOf(objectIds, 20);
+// for (int i = 0; i < paths.length; ++i) {
+// if (i >= maxEntries) break;
+// final path = paths[i];
+// print('There are ${path.count} retaining paths of');
+// print(formatRetainingPath(graph, paths[i]));
+// print('');
+// }
+// }
+//
+// IntSet filterByClass(
+// IntSet objectIds, {
+// required Uri libraryUri,
+// required String name,
+// }) {
+// return filter(reachableObjects, (object) {
+// return object.klass.libraryUri == libraryUri && object.klass.name == name;
+// });
+// }
+// }
diff --git a/analyzer/example/ddd04.dart b/analyzer/example/ddd04.dart
index 0a21f3f..881560c 100644
--- a/analyzer/example/ddd04.dart
+++ b/analyzer/example/ddd04.dart
@@ -15,9 +15,9 @@
// /// --observe:5000 --disable-service-auth-codes --pause_isolates_on_unhandled_exceptions=false --pause_isolates_on_exit=false
// void main() async {
// // var path = '/Users/scheglov/dart/rwf-materials';
-// // var path = '/Users/scheglov/dart/rwf-materials/01-setting-up-your-environment';
-// var path =
-// '/Users/scheglov/dart/rwf-materials/01-setting-up-your-environment/projects/starter/packages/component_library';
+// var path = '/Users/scheglov/dart/rwf-materials/01-setting-up-your-environment';
+// // var path =
+// // '/Users/scheglov/dart/rwf-materials/01-setting-up-your-environment/projects/starter/packages/component_library';
//
// // var profiler = ProcessProfiler.getProfilerForPlatform()!;
// while (true) {
@@ -74,151 +74,151 @@
// timer.stop();
// print('[time: ${timer.elapsedMilliseconds} ms]');
//
-// {
-// var timer = Stopwatch()..start();
-// var chunks = await loadFromUri(Uri.parse('http://127.0.0.1:5000'));
-// // final length = chunks
-// // .map((e) => e.lengthInBytes)
-// // .fold<int>(0, (prev, e) => prev + e);
-// // print(
-// // ' [${timer.elapsedMilliseconds} ms] '
-// // 'Downloaded heap snapshot, ${length / 1024 / 1024} MB.',
-// // );
-//
-// if (0 == 1) {
-// final bytesBuilder = BytesBuilder();
-// for (final chunk in chunks) {
-// bytesBuilder.add(
-// chunk.buffer.asUint8List(
-// chunk.offsetInBytes,
-// chunk.lengthInBytes,
-// ),
-// );
-// }
-// final bytes = bytesBuilder.toBytes();
-// final path = '/Users/scheglov/tmp/01.heap_snapshot';
-// File(path).writeAsBytesSync(bytes);
-// final lengthStr = (bytes.length / 1024 / 1024).toStringAsFixed(2);
-// print('Stored $lengthStr MB into $path');
-// }
-//
-// final graph = HeapSnapshotGraph.fromChunks(chunks);
-// print(' [${timer.elapsedMilliseconds} ms] Created HeapSnapshotGraph.');
-// print(' externalSize: ${graph.externalSize}');
-// print(' shallowSize: ${graph.shallowSize}');
-// print(' Objects: ${graph.objects.length}');
-//
-// final analysis = Analysis(graph);
-// print(' [${timer.elapsedMilliseconds} ms] Created Analysis.');
-//
-// {
-// print('All objects.');
-// final objects = analysis.reachableObjects;
-// final stats = analysis.generateObjectStats(objects);
-// print(formatHeapStats(stats, maxLines: 20));
-// print('');
-// }
-//
-// {
-// print('FileState(s)');
-// var fileStateList = analysis.filter(
-// analysis.reachableObjects,
-// (object) {
-// return object.klass.name == 'FileState';
-// },
-// );
-// analysis.printObjectStats(fileStateList);
-// print('');
-// final allObjects = analysis.transitiveGraph(fileStateList);
-// analysis.printObjectStats(allObjects);
-// print('');
-// }
-//
-// if (0 == 1) {
-// print('Instances of: _SimpleUri');
-// final uriList = analysis.filterByClassPatterns(
-// analysis.reachableObjects,
-// ['_SimpleUri'],
-// );
-// final stats = analysis.generateObjectStats(uriList);
-// print(formatHeapStats(stats, maxLines: 20));
-// print('');
-//
-// final uriStringList = analysis.findReferences(uriList, [':_uri']);
-//
-// // TODO(scheglov) Restore
-// final uniqueUriStrSet = Set<String>();
-// for (final objectId in uriStringList) {
-// var object = graph.objects[objectId];
-// var uriStr = object.data as String;
-// if (!uniqueUriStrSet.add(uriStr)) {
-// throw StateError('Duplicate URI: $uriStr');
-// }
-// }
-//
-// final dstats = analysis.generateDataStats(uriStringList);
-// print(formatDataStats(dstats, maxLines: 20));
-// }
-//
-// if (0 == 0) {
-// print('Instances of: LibraryElementImpl');
-// final uriList = analysis.filterByClassPatterns(
-// analysis.reachableObjects,
-// ['LibraryElementImpl'],
-// );
-// final stats = analysis.generateObjectStats(uriList);
-// print(formatHeapStats(stats, maxLines: 20));
-// print('');
-// }
-//
-// if (0 == 0) {
-// print('Instances of: _GrowableList');
-// final objectList = analysis.filter(analysis.reachableObjects, (object) {
-// return object.klass.libraryUri == Uri.parse('dart:core') &&
-// object.klass.name == '_GrowableList';
-// // return analysis.variableLengthOf(object) == 0;
-// });
-//
-// // final objectList = analysis.filterByClassPatterns(
-// // analysis.reachableObjects,
-// // ['_GrowableList'],
-// // );
-// final stats = analysis.generateObjectStats(objectList);
-// print(formatHeapStats(stats, maxLines: 20));
-// print('');
-//
-// const maxEntries = 10;
-// final paths = analysis.retainingPathsOf(objectList, 10);
-// for (int i = 0; i < paths.length; ++i) {
-// if (maxEntries != -1 && i >= maxEntries) break;
-// final path = paths[i];
-// print('There are ${path.count} retaining paths of');
-// print(formatRetainingPath(analysis.graph, paths[i]));
-// print('');
-// }
-//
-// {
-// print('Instances of empty: _GrowableList');
-// final emptyList = analysis.filter(objectList, (object) {
-// return analysis.variableLengthOf(object) == 0;
-// });
-// final stats = analysis.generateObjectStats(emptyList);
-// print(formatHeapStats(stats, maxLines: 20));
-// print('');
-//
-// // final paths = analysis.retainingPathsOf(emptyList, 10);
-// // for (int i = 0; i < paths.length; ++i) {
-// // if (maxEntries != -1 && i >= maxEntries) break;
-// // final path = paths[i];
-// // print('There are ${path.count} retaining paths of');
-// // print(formatRetainingPath(analysis.graph, paths[i]));
-// // print('');
-// // }
-// }
-// // final dstats = analysis.generateDataStats(uriStringList);
-// // print(formatDataStats(dstats, maxLines: 20));
-// }
-// }
+// // {
+// // var timer = Stopwatch()..start();
+// // var chunks = await loadFromUri(Uri.parse('http://127.0.0.1:5000'));
+// // // final length = chunks
+// // // .map((e) => e.lengthInBytes)
+// // // .fold<int>(0, (prev, e) => prev + e);
+// // // print(
+// // // ' [${timer.elapsedMilliseconds} ms] '
+// // // 'Downloaded heap snapshot, ${length / 1024 / 1024} MB.',
+// // // );
+// //
+// // if (0 == 1) {
+// // final bytesBuilder = BytesBuilder();
+// // for (final chunk in chunks) {
+// // bytesBuilder.add(
+// // chunk.buffer.asUint8List(
+// // chunk.offsetInBytes,
+// // chunk.lengthInBytes,
+// // ),
+// // );
+// // }
+// // final bytes = bytesBuilder.toBytes();
+// // final path = '/Users/scheglov/tmp/01.heap_snapshot';
+// // File(path).writeAsBytesSync(bytes);
+// // final lengthStr = (bytes.length / 1024 / 1024).toStringAsFixed(2);
+// // print('Stored $lengthStr MB into $path');
+// // }
+// //
+// // final graph = HeapSnapshotGraph.fromChunks(chunks);
+// // print(' [${timer.elapsedMilliseconds} ms] Created HeapSnapshotGraph.');
+// // print(' externalSize: ${graph.externalSize}');
+// // print(' shallowSize: ${graph.shallowSize}');
+// // print(' Objects: ${graph.objects.length}');
+// //
+// // final analysis = Analysis(graph);
+// // print(' [${timer.elapsedMilliseconds} ms] Created Analysis.');
+// //
+// // {
+// // print('All objects.');
+// // final objects = analysis.reachableObjects;
+// // final stats = analysis.generateObjectStats(objects);
+// // print(formatHeapStats(stats, maxLines: 20));
+// // print('');
+// // }
+// //
+// // {
+// // print('FileState(s)');
+// // var fileStateList = analysis.filter(
+// // analysis.reachableObjects,
+// // (object) {
+// // return object.klass.name == 'FileState';
+// // },
+// // );
+// // analysis.printObjectStats(fileStateList);
+// // print('');
+// // final allObjects = analysis.transitiveGraph(fileStateList);
+// // analysis.printObjectStats(allObjects);
+// // print('');
+// // }
+// //
+// // if (0 == 1) {
+// // print('Instances of: _SimpleUri');
+// // final uriList = analysis.filterByClassPatterns(
+// // analysis.reachableObjects,
+// // ['_SimpleUri'],
+// // );
+// // final stats = analysis.generateObjectStats(uriList);
+// // print(formatHeapStats(stats, maxLines: 20));
+// // print('');
+// //
+// // final uriStringList = analysis.findReferences(uriList, [':_uri']);
+// //
+// // // TODO(scheglov) Restore
+// // final uniqueUriStrSet = Set<String>();
+// // for (final objectId in uriStringList) {
+// // var object = graph.objects[objectId];
+// // var uriStr = object.data as String;
+// // if (!uniqueUriStrSet.add(uriStr)) {
+// // throw StateError('Duplicate URI: $uriStr');
+// // }
+// // }
+// //
+// // final dstats = analysis.generateDataStats(uriStringList);
+// // print(formatDataStats(dstats, maxLines: 20));
+// // }
+// //
+// // if (0 == 0) {
+// // print('Instances of: LibraryElementImpl');
+// // final uriList = analysis.filterByClassPatterns(
+// // analysis.reachableObjects,
+// // ['LibraryElementImpl'],
+// // );
+// // final stats = analysis.generateObjectStats(uriList);
+// // print(formatHeapStats(stats, maxLines: 20));
+// // print('');
+// // }
+// //
+// // if (0 == 0) {
+// // print('Instances of: _GrowableList');
+// // final objectList = analysis.filter(analysis.reachableObjects, (object) {
+// // return object.klass.libraryUri == Uri.parse('dart:core') &&
+// // object.klass.name == '_GrowableList';
+// // // return analysis.variableLengthOf(object) == 0;
+// // });
+// //
+// // // final objectList = analysis.filterByClassPatterns(
+// // // analysis.reachableObjects,
+// // // ['_GrowableList'],
+// // // );
+// // final stats = analysis.generateObjectStats(objectList);
+// // print(formatHeapStats(stats, maxLines: 20));
+// // print('');
+// //
+// // const maxEntries = 10;
+// // final paths = analysis.retainingPathsOf(objectList, 10);
+// // for (int i = 0; i < paths.length; ++i) {
+// // if (maxEntries != -1 && i >= maxEntries) break;
+// // final path = paths[i];
+// // print('There are ${path.count} retaining paths of');
+// // print(formatRetainingPath(analysis.graph, paths[i]));
+// // print('');
+// // }
+// //
+// // {
+// // print('Instances of empty: _GrowableList');
+// // final emptyList = analysis.filter(objectList, (object) {
+// // return analysis.variableLengthOf(object) == 0;
+// // });
+// // final stats = analysis.generateObjectStats(emptyList);
+// // print(formatHeapStats(stats, maxLines: 20));
+// // print('');
+// //
+// // // final paths = analysis.retainingPathsOf(emptyList, 10);
+// // // for (int i = 0; i < paths.length; ++i) {
+// // // if (maxEntries != -1 && i >= maxEntries) break;
+// // // final path = paths[i];
+// // // print('There are ${path.count} retaining paths of');
+// // // print(formatRetainingPath(analysis.graph, paths[i]));
+// // // print('');
+// // // }
+// // }
+// // // final dstats = analysis.generateDataStats(uriStringList);
+// // // print(formatDataStats(dstats, maxLines: 20));
+// // }
+// // }
//
// break;
//
@@ -234,34 +234,34 @@
// // await Future<void>.delayed(const Duration(days: 1));
// }
//
-// extension on Analysis {
-// void printObjectStats(IntSet objectIds) {
-// final stats = generateObjectStats(objectIds);
-// print(formatHeapStats(stats, maxLines: 20));
-// print('');
-// }
-//
-// void printRetainers(
-// IntSet objectIds, {
-// int maxEntries = 3,
-// }) {
-// final paths = retainingPathsOf(objectIds, 20);
-// for (int i = 0; i < paths.length; ++i) {
-// if (i >= maxEntries) break;
-// final path = paths[i];
-// print('There are ${path.count} retaining paths of');
-// print(formatRetainingPath(graph, paths[i]));
-// print('');
-// }
-// }
-//
-// IntSet filterByClass(
-// IntSet objectIds, {
-// required Uri libraryUri,
-// required String name,
-// }) {
-// return filter(reachableObjects, (object) {
-// return object.klass.libraryUri == libraryUri && object.klass.name == name;
-// });
-// }
-// }
+// // extension on Analysis {
+// // void printObjectStats(IntSet objectIds) {
+// // final stats = generateObjectStats(objectIds);
+// // print(formatHeapStats(stats, maxLines: 20));
+// // print('');
+// // }
+// //
+// // void printRetainers(
+// // IntSet objectIds, {
+// // int maxEntries = 3,
+// // }) {
+// // final paths = retainingPathsOf(objectIds, 20);
+// // for (int i = 0; i < paths.length; ++i) {
+// // if (i >= maxEntries) break;
+// // final path = paths[i];
+// // print('There are ${path.count} retaining paths of');
+// // print(formatRetainingPath(graph, paths[i]));
+// // print('');
+// // }
+// // }
+// //
+// // IntSet filterByClass(
+// // IntSet objectIds, {
+// // required Uri libraryUri,
+// // required String name,
+// // }) {
+// // return filter(reachableObjects, (object) {
+// // return object.klass.libraryUri == libraryUri && object.klass.name == name;
+// // });
+// // }
+// // }
diff --git a/analyzer/lib/dart/analysis/analysis_options.dart b/analyzer/lib/dart/analysis/analysis_options.dart
index 05fbbaf..ddeab65 100644
--- a/analyzer/lib/dart/analysis/analysis_options.dart
+++ b/analyzer/lib/dart/analysis/analysis_options.dart
@@ -35,8 +35,8 @@
/// analysis.
List<String> get excludePatterns;
- /// Return `true` if analysis is to generate hint results (e.g. type inference
- /// based information and pub best practices).
+ /// Return `true` if analysis is to generate hint results (e.g. best practices
+ /// and analysis based on certain annotations).
bool get hint;
/// Return `true` if analysis is to generate lint warnings.
diff --git a/analyzer/lib/dart/ast/ast.dart b/analyzer/lib/dart/ast/ast.dart
index a146d77..307462a 100644
--- a/analyzer/lib/dart/ast/ast.dart
+++ b/analyzer/lib/dart/ast/ast.dart
@@ -41,9 +41,6 @@
import 'package:analyzer/src/generated/source.dart' show LineInfo;
import 'package:meta/meta.dart';
-@Deprecated('Use PatternField and visitPatternField() instead')
-typedef RecordPatternField = PatternField;
-
/// Two or more string literals that are implicitly concatenated because of
/// being adjacent (separated only by whitespace).
///
@@ -1011,6 +1008,10 @@
@override
ImplementsClause? get implementsClause;
+ /// Return the 'inline' keyword, or `null` if the keyword was absent.
+ @experimental
+ Token? get inlineKeyword;
+
/// Returns the left curly bracket.
@override
Token get leftBracket;
@@ -1959,6 +1960,9 @@
/// not be resolved.
ConstructorElement? get constructorElement;
+ @override
+ FieldElement? get declaredElement;
+
/// Return the name of the constant.
Token get name;
@@ -2687,6 +2691,7 @@
/// level function or method containing this [FunctionBody], return `false`.
///
/// Throws an exception if resolution has not yet been performed.
+ @Deprecated('Not used by clients')
bool isPotentiallyMutatedInClosure(VariableElement variable);
/// If [variable] is a local variable or parameter declared anywhere within
@@ -4277,8 +4282,13 @@
/// Clients may not extend, implement or mix-in this class.
@experimental
abstract class PatternField implements AstNode {
- /// The element referenced explicitly by [name], or implicitly by the
- /// variable pattern inside [pattern]. Is `null` if not resolved yet,
+ /// The name specified explicitly by [name], or implied by the variable
+ /// pattern inside [pattern]. Always `null` if [name] is `null`. Can be `null`
+ /// if [name] does not have the explicit name and [pattern] is not a variable
+ /// pattern.
+ String? get effectiveName;
+
+ /// The element referenced by [effectiveName]. Is `null` if not resolved yet,
/// not `null` inside valid [ObjectPattern]s, always `null` inside
/// [RecordPattern]s.
Element? get element;
diff --git a/analyzer/lib/dart/ast/visitor.dart b/analyzer/lib/dart/ast/visitor.dart
index 770a231..42064b5 100644
--- a/analyzer/lib/dart/ast/visitor.dart
+++ b/analyzer/lib/dart/ast/visitor.dart
@@ -2163,9 +2163,6 @@
@override
R? visitRecordPattern(RecordPattern node) => null;
- @Deprecated('Use visitPatternField() instead')
- void visitRecordPatternField(RecordPatternField node) {}
-
@override
R? visitRecordTypeAnnotation(RecordTypeAnnotation node) => null;
diff --git a/analyzer/lib/dart/element/element.dart b/analyzer/lib/dart/element/element.dart
index 4e9d67f..b56fe1e 100644
--- a/analyzer/lib/dart/element/element.dart
+++ b/analyzer/lib/dart/element/element.dart
@@ -200,11 +200,18 @@
/// <i>abstract</i> is different from <i>has unimplemented members</i>.
bool get isAbstract;
- /// Return `true` if this class is a base class. A class is a base class if it
- /// has an explicit `base` modifier.
+ /// Return `true` if this class is a base class.
+ ///
+ /// A class is a base class if it has an explicit `base` modifier, or the
+ /// class has a `base` induced modifier and [isSealed] is `true` as well.
+ /// The base modifier allows the class to be extended but not implemented.
@experimental
bool get isBase;
+ /// Return `true` if this class can be instantiated.
+ @experimental
+ bool get isConstructable;
+
/// Return `true` if this class represents the class 'Enum' defined in the
/// dart:core library.
bool get isDartCoreEnum;
@@ -219,13 +226,21 @@
@experimental
bool get isExhaustive;
- /// Return `true` if this class is a final class. A class is a final class if
- /// it has an explicit `final` modifier.
+ /// Return `true` if this class is a final class.
+ ///
+ /// A class is a final class if it has an explicit `final` modifier, or the
+ /// class has a `final` induced modifier and [isSealed] is `true` as well.
+ /// The final modifier prohibits this class from being extended, implemented,
+ /// or mixed in.
@experimental
bool get isFinal;
- /// Return `true` if this class is an interface class. A class is an interface
- /// class if it has an explicit `interface` modifier.
+ /// Return `true` if this class is an interface class.
+ ///
+ /// A class is an interface class if it has an explicit `interface` modifier,
+ /// or the class has an `interface` induced modifier and [isSealed] is `true`
+ /// as well. The interface modifier allows the class to be implemented, but
+ /// not extended or mixed in.
@experimental
bool get isInterface;
@@ -724,6 +739,23 @@
/// Return the analysis session in which this element is defined.
AnalysisSession? get session;
+ /// The version where this SDK API was added.
+ ///
+ /// A `@Since()` annotation can be applied to a library declaration,
+ /// any public declaration in a library, or in a class, or to an optional
+ /// parameter, etc.
+ ///
+ /// The returned version is "effective", so that if a library is annotated
+ /// then all elements of the library inherit it; or if a class is annotated
+ /// then all members and constructors of the class inherit it.
+ ///
+ /// If multiple `@Since()` annotations apply to the same element, the latest
+ /// version takes precedence.
+ ///
+ /// Returns `null` if the element is not declared in SDK, or does not have
+ /// a `@Since()` annotation applicable to it.
+ Version? get sinceSdkVersion;
+
@override
Source? get source;
@@ -1986,8 +2018,11 @@
/// Returns the result of applying augmentations to this element.
AugmentedMixinElement get augmented;
- /// Return `true` if this mixin is a base mixin. A mixin is a base mixin if it
- /// has an explicit `base` modifier.
+ /// Return `true` if this mixin is a base mixin.
+ ///
+ /// A mixin is a base mixin if it has an explicit `base` modifier, or the
+ /// mixin has a `base` induced modifier and [isSealed] is `true` as well.
+ /// The base modifier allows a mixin to be mixed in but not implemented.
@experimental
bool get isBase;
diff --git a/analyzer/lib/dart/element/type.dart b/analyzer/lib/dart/element/type.dart
index fcb6243..31fece1 100644
--- a/analyzer/lib/dart/element/type.dart
+++ b/analyzer/lib/dart/element/type.dart
@@ -118,10 +118,15 @@
/// dart:core library.
bool get isDartCoreSymbol;
+ /// Return `true` if this type represents the type 'Type' defined in the
+ /// dart:core library.
+ bool get isDartCoreType;
+
/// Return `true` if this type represents the type 'dynamic'.
bool get isDynamic;
/// Return `true` if this type represents the type 'void'.
+ @Deprecated('Use `is VoidType` instead')
bool get isVoid;
/// Return the name of this type, or `null` if the type does not have a name,
diff --git a/analyzer/lib/error/listener.dart b/analyzer/lib/error/listener.dart
index ee23979..44e7701 100644
--- a/analyzer/lib/error/listener.dart
+++ b/analyzer/lib/error/listener.dart
@@ -73,10 +73,10 @@
/// Report an error with the given [errorCode] and [arguments]. The [element]
/// is used to compute the location of the error.
void reportErrorForElement(ErrorCode errorCode, Element element,
- [List<Object>? arguments]) {
+ [List<Object>? arguments, List<DiagnosticMessage>? messages]) {
var nonSynthetic = element.nonSynthetic;
- reportErrorForOffset(
- errorCode, nonSynthetic.nameOffset, nonSynthetic.nameLength, arguments);
+ reportErrorForOffset(errorCode, nonSynthetic.nameOffset,
+ nonSynthetic.nameLength, arguments, messages);
}
/// Report a diagnostic with the given [code] and [arguments]. The
diff --git a/analyzer/lib/file_system/file_system.dart b/analyzer/lib/file_system/file_system.dart
index e0f9c23..1d38d61 100644
--- a/analyzer/lib/file_system/file_system.dart
+++ b/analyzer/lib/file_system/file_system.dart
@@ -149,6 +149,15 @@
ResourceWatcher watch();
}
+/// Exception thrown when a file operation fails because a file or directory
+/// does not exist.
+class PathNotFoundException extends FileSystemException {
+ PathNotFoundException(super.path, super.message);
+
+ @override
+ String toString() => 'PathNotFoundException(path=$path; message=$message)';
+}
+
/// The abstract class [Resource] is an abstraction of file or folder.
abstract class Resource {
/// Return `true` if this resource exists.
diff --git a/analyzer/lib/file_system/memory_file_system.dart b/analyzer/lib/file_system/memory_file_system.dart
index 4f6a36f..c338d10 100644
--- a/analyzer/lib/file_system/memory_file_system.dart
+++ b/analyzer/lib/file_system/memory_file_system.dart
@@ -33,6 +33,11 @@
@visibleForTesting
final Duration? delayWatcherInitialization;
+ /// Paths that should have `PathNotFoundException`s emitted on their watch
+ /// streams.
+ @visibleForTesting
+ final Set<String> emitPathNotFoundExceptionsForPaths = {};
+
MemoryResourceProvider({
pathos.Context? context,
this.delayWatcherInitialization,
@@ -625,6 +630,10 @@
}
});
ready.complete();
+ if (provider.emitPathNotFoundExceptionsForPaths.contains(path)) {
+ streamController.addError(PathNotFoundException(
+ path, 'Simulated PathNotFoundException from _MemoryResource'));
+ }
}
final delayWatcherInitialization = provider.delayWatcherInitialization;
diff --git a/analyzer/lib/file_system/physical_file_system.dart b/analyzer/lib/file_system/physical_file_system.dart
index 962862f..c87286a 100644
--- a/analyzer/lib/file_system/physical_file_system.dart
+++ b/analyzer/lib/file_system/physical_file_system.dart
@@ -2,6 +2,7 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
+import 'dart:async';
import 'dart:io' as io;
import 'dart:typed_data';
@@ -183,7 +184,7 @@
@override
ResourceWatcher watch() {
final watcher = FileWatcher(_entry.path);
- return ResourceWatcher(watcher.events, watcher.ready);
+ return ResourceWatcher(_wrapWatcherStream(watcher.events), watcher.ready);
}
@override
@@ -319,7 +320,7 @@
// Don't suppress "Directory watcher closed," so the outer
// listener can see the interruption & act on it.
!error.message.startsWith("Directory watcher closed unexpectedly"));
- return ResourceWatcher(events, watcher.ready);
+ return ResourceWatcher(_wrapWatcherStream(events), watcher.ready);
}
}
@@ -408,6 +409,28 @@
}
FileSystemException _wrapException(io.FileSystemException e) {
- return FileSystemException(e.path ?? path, e.message);
+ if (e is io.PathNotFoundException) {
+ return PathNotFoundException(e.path ?? path, e.message);
+ } else {
+ return FileSystemException(e.path ?? path, e.message);
+ }
+ }
+
+ /// Wraps a `Stream<WatchEvent>` to map all known errors through
+ /// [_wrapException] into server types.
+ Stream<WatchEvent> _wrapWatcherStream(Stream<WatchEvent> original) {
+ /// Helper to map thrown `FileSystemException`s to servers abstraction.
+ Object mapException(Object e) {
+ return e is io.FileSystemException ? _wrapException(e) : e;
+ }
+
+ final mappedEventsController = StreamController<WatchEvent>();
+ final subscription = original.listen(
+ mappedEventsController.add,
+ onError: (Object e) => mappedEventsController.addError(mapException(e)),
+ onDone: () => mappedEventsController.close(),
+ );
+ mappedEventsController.onCancel = subscription.cancel;
+ return mappedEventsController.stream;
}
}
diff --git a/analyzer/lib/source/error_processor.dart b/analyzer/lib/source/error_processor.dart
index 207f15a..e34dad3 100644
--- a/analyzer/lib/source/error_processor.dart
+++ b/analyzer/lib/source/error_processor.dart
@@ -24,7 +24,7 @@
/// Create an error config for the given error code map.
/// For example:
/// new ErrorConfig({'missing_return' : 'error'});
- /// will create a processor config that turns `missing_return` hints into
+ /// will create a processor config that turns `missing_return` warnings into
/// errors.
ErrorConfig(YamlNode? codeMap) {
_processMap(codeMap);
diff --git a/analyzer/lib/src/dart/analysis/byte_store.dart b/analyzer/lib/src/dart/analysis/byte_store.dart
index ea166eb..c440b5c 100644
--- a/analyzer/lib/src/dart/analysis/byte_store.dart
+++ b/analyzer/lib/src/dart/analysis/byte_store.dart
@@ -24,10 +24,12 @@
/// Associate [bytes] with [key].
///
- /// If this store supports reference counting, returns the internalized
- /// version of [bytes], the reference count is set to `1`.
- ///
- /// TODO(scheglov) Disable overwriting.
+ /// If this store supports reference counting:
+ /// 1. If there is already data with [key], increments the count and
+ /// returns the existing data. This can happen when multiple isolates work
+ /// with the same store (via native code).
+ /// 2. Otherwise, returns the internalized version of [bytes], the reference
+ /// count is set to `1`.
Uint8List putGet(String key, Uint8List bytes);
/// If this store supports reference counting, decrements it for every key
@@ -40,6 +42,10 @@
@visibleForTesting
final Map<String, MemoryByteStoreEntry> map = {};
+ /// Throws [StateError] if [release] invoked when there is no entry.
+ @visibleForTesting
+ bool throwIfReleaseWithoutEntry = false;
+
@override
Uint8List? get(String key) {
final entry = map[key];
@@ -53,6 +59,12 @@
@override
Uint8List putGet(String key, Uint8List bytes) {
+ final entry = map[key];
+ if (entry != null) {
+ entry.refCount++;
+ return entry.bytes;
+ }
+
map[key] = MemoryByteStoreEntry._(bytes);
return bytes;
}
@@ -66,6 +78,8 @@
if (entry.refCount == 0) {
map.remove(key);
}
+ } else if (throwIfReleaseWithoutEntry) {
+ throw StateError('No entry: $key');
}
}
}
diff --git a/analyzer/lib/src/dart/analysis/driver.dart b/analyzer/lib/src/dart/analysis/driver.dart
index 690064c..25bfda6 100644
--- a/analyzer/lib/src/dart/analysis/driver.dart
+++ b/analyzer/lib/src/dart/analysis/driver.dart
@@ -87,7 +87,7 @@
/// TODO(scheglov) Clean up the list of implicitly analyzed files.
class AnalysisDriver implements AnalysisDriverGeneric {
/// The version of data format, should be incremented on every format change.
- static const int DATA_VERSION = 259;
+ static const int DATA_VERSION = 264;
/// The number of exception contexts allowed to write. Once this field is
/// zero, we stop writing any new exception contexts in this process.
diff --git a/analyzer/lib/src/dart/analysis/experiments.g.dart b/analyzer/lib/src/dart/analysis/experiments.g.dart
index 69bb459..8479b45 100644
--- a/analyzer/lib/src/dart/analysis/experiments.g.dart
+++ b/analyzer/lib/src/dart/analysis/experiments.g.dart
@@ -141,7 +141,7 @@
isExpired: IsExpired.class_modifiers,
documentation: 'Class modifiers',
experimentalReleaseVersion: null,
- releaseVersion: null,
+ releaseVersion: Version.parse('3.0.0'),
);
static final const_functions = ExperimentalFeature(
@@ -305,7 +305,7 @@
isExpired: IsExpired.patterns,
documentation: 'Patterns',
experimentalReleaseVersion: null,
- releaseVersion: null,
+ releaseVersion: Version.parse('3.0.0'),
);
static final records = ExperimentalFeature(
@@ -314,8 +314,8 @@
isEnabledByDefault: IsEnabledByDefault.records,
isExpired: IsExpired.records,
documentation: 'Records',
- experimentalReleaseVersion: Version.parse('2.19.0'),
- releaseVersion: null,
+ experimentalReleaseVersion: null,
+ releaseVersion: Version.parse('3.0.0'),
);
static final sealed_class = ExperimentalFeature(
@@ -325,7 +325,7 @@
isExpired: IsExpired.sealed_class,
documentation: 'Sealed class',
experimentalReleaseVersion: null,
- releaseVersion: null,
+ releaseVersion: Version.parse('3.0.0'),
);
static final set_literals = ExperimentalFeature(
@@ -414,7 +414,7 @@
/// enabled by default.
class IsEnabledByDefault {
/// Default state of the experiment "class-modifiers"
- static const bool class_modifiers = false;
+ static const bool class_modifiers = true;
/// Default state of the experiment "const-functions"
static const bool const_functions = false;
@@ -462,13 +462,13 @@
static const bool nonfunction_type_aliases = true;
/// Default state of the experiment "patterns"
- static const bool patterns = false;
+ static const bool patterns = true;
/// Default state of the experiment "records"
- static const bool records = false;
+ static const bool records = true;
/// Default state of the experiment "sealed-class"
- static const bool sealed_class = false;
+ static const bool sealed_class = true;
/// Default state of the experiment "set-literals"
static const bool set_literals = true;
diff --git a/analyzer/lib/src/dart/analysis/file_state.dart b/analyzer/lib/src/dart/analysis/file_state.dart
index 4aab0f0..5db207d 100644
--- a/analyzer/lib/src/dart/analysis/file_state.dart
+++ b/analyzer/lib/src/dart/analysis/file_state.dart
@@ -690,6 +690,7 @@
var bytes = driverUnlinkedUnit.toBytes();
_fsState._byteStore.putGet(_unlinkedKey!, bytes);
testData?.unlinkedKeyPut.add(unlinkedKey);
+ _fsState.unlinkedUnitStore.put(_unlinkedKey!, driverUnlinkedUnit);
return driverUnlinkedUnit;
});
}
@@ -1196,6 +1197,9 @@
// The removed file does not reference other files anymore.
file._kind?.dispose();
+ // Release this unlinked data.
+ unlinkedUnitStore.release(file.unlinkedKey);
+
// Recursively remove files that reference the removed file.
for (var reference in file.referencingFiles.toList()) {
changeFile(reference.path, removedFiles);
diff --git a/analyzer/lib/src/dart/analysis/index.dart b/analyzer/lib/src/dart/analysis/index.dart
index b5538b9..8179edc 100644
--- a/analyzer/lib/src/dart/analysis/index.dart
+++ b/analyzer/lib/src/dart/analysis/index.dart
@@ -294,9 +294,22 @@
nameRelations.add(_NameRelationInfo(nameId, kind, offset, isQualified));
}
- void addPrefixForElement(PrefixElement prefixElement, Element element) {
+ /// Adds a prefix (or empty string for unprefixed) for an element.
+ void addPrefixForElement(Element element, {PrefixElement? prefix}) {
+ if (element is MultiplyDefinedElementImpl ||
+ // TODO(brianwilkerson) The last two conditions are here because the
+ // elements for `dynamic` and `Never` are singletons and hence don't have
+ // a parent element for which we can find an `_ElementInfo`. This means
+ // that any reference to either type via a prefix can't be stored in the
+ // index. The solution is to make those elements be normal (not unique)
+ // elements.
+ element is DynamicElementImpl ||
+ element is NeverElementImpl) {
+ return;
+ }
+
_ElementInfo elementInfo = _getElementInfo(element);
- elementInfo.importPrefixes.add(prefixElement.name);
+ elementInfo.importPrefixes.add(prefix?.name ?? '');
}
void addSubtype(String name, List<String> members, List<String> supertypes) {
@@ -833,20 +846,10 @@
@override
void visitPrefixedIdentifier(PrefixedIdentifier node) {
- var prefixElement = node.prefix.staticElement;
var element = node.staticElement;
- if (element != null &&
- prefixElement is PrefixElement &&
- element is! MultiplyDefinedElementImpl &&
- element is! DynamicElementImpl &&
- element is! NeverElementImpl) {
- // TODO(brianwilkerson) The last two conditions are here because the
- // elements for `dynamic` and `Never` are singletons and hence don't have
- // a parent element for which we can find an `_ElementInfo`. This means
- // that any reference to either type via a prefix can't be stored in the
- // index. The solution is to make those elements be normal (not unique)
- // elements.
- assembler.addPrefixForElement(prefixElement, element);
+ var prefixElement = node.prefix.staticElement;
+ if (element != null && prefixElement is PrefixElement) {
+ assembler.addPrefixForElement(element, prefix: prefixElement);
}
super.visitPrefixedIdentifier(node);
}
@@ -886,6 +889,15 @@
element = declaredParameterElement(node, element);
}
+ final parent = node.parent;
+ if (element != null &&
+ element.enclosingElement is CompilationUnitElement &&
+ // We're only unprefixed when part of a PrefixedIdentifier if we're
+ // the left side.
+ (parent is! PrefixedIdentifier || parent.prefix == node)) {
+ assembler.addPrefixForElement(element);
+ }
+
// record unresolved name reference
bool isQualified = _isQualified(node);
if (element == null) {
diff --git a/analyzer/lib/src/dart/analysis/library_analyzer.dart b/analyzer/lib/src/dart/analysis/library_analyzer.dart
index eede372..accd399 100644
--- a/analyzer/lib/src/dart/analysis/library_analyzer.dart
+++ b/analyzer/lib/src/dart/analysis/library_analyzer.dart
@@ -250,7 +250,7 @@
);
}
- /// Compute diagnostics in [units], including errors and warnings, hints,
+ /// Compute diagnostics in [units], including errors and warnings,
/// lints, and a few other cases.
void _computeDiagnostics(Map<FileState, CompilationUnitImpl> units) {
units.forEach((file, unit) {
@@ -274,7 +274,7 @@
}
var usedElements = UsedLocalElements.merge(usedLocalElements);
units.forEach((file, unit) {
- _computeHints(
+ _computeWarnings(
file,
unit,
usedImportedElements: usedImportedElements,
@@ -322,85 +322,6 @@
}
}
- void _computeHints(
- FileState file,
- CompilationUnit unit, {
- required List<UsedImportedElements> usedImportedElements,
- required UsedLocalElements usedElements,
- }) {
- AnalysisErrorListener errorListener = _getErrorListener(file);
- ErrorReporter errorReporter = _getErrorReporter(file);
-
- if (!_libraryElement.isNonNullableByDefault) {
- unit.accept(
- LegacyDeadCodeVerifier(
- errorReporter,
- typeSystem: _typeSystem,
- ),
- );
- }
-
- UnicodeTextVerifier(errorReporter).verify(unit, file.content);
-
- unit.accept(DeadCodeVerifier(errorReporter));
-
- unit.accept(
- BestPracticesVerifier(
- errorReporter,
- _typeProvider,
- _libraryElement,
- unit,
- file.content,
- declaredVariables: _declaredVariables,
- typeSystem: _typeSystem,
- inheritanceManager: _inheritance,
- analysisOptions: _analysisOptions,
- workspacePackage: _library.file.workspacePackage,
- ),
- );
-
- unit.accept(OverrideVerifier(
- _inheritance,
- _libraryElement,
- errorReporter,
- ));
-
- TodoFinder(errorReporter).findIn(unit);
- LanguageVersionOverrideVerifier(errorReporter).verify(unit);
-
- // Verify imports.
- {
- ImportsVerifier verifier = ImportsVerifier();
- verifier.addImports(unit);
- usedImportedElements.forEach(verifier.removeUsedElements);
- verifier.generateDuplicateExportHints(errorReporter);
- verifier.generateDuplicateImportHints(errorReporter);
- verifier.generateDuplicateShownHiddenNameHints(errorReporter);
- verifier.generateUnusedImportHints(errorReporter);
- verifier.generateUnusedShownNameHints(errorReporter);
- verifier.generateUnnecessaryImportHints(
- errorReporter, usedImportedElements);
- }
-
- // Unused local elements.
- {
- UnusedLocalElementsVerifier visitor = UnusedLocalElementsVerifier(
- errorListener, usedElements, _inheritance, _libraryElement);
- unit.accept(visitor);
- }
-
- //
- // Find code that uses features from an SDK version that does not satisfy
- // the SDK constraints specified in analysis options.
- //
- var sdkVersionConstraint = _analysisOptions.sdkVersionConstraint;
- if (sdkVersionConstraint != null) {
- SdkConstraintVerifier verifier = SdkConstraintVerifier(
- errorReporter, _libraryElement, _typeProvider, sdkVersionConstraint);
- unit.accept(verifier);
- }
- }
-
void _computeLints(
FileState file,
LinterContextUnit currentUnit,
@@ -478,6 +399,85 @@
unit.accept(FfiVerifier(_typeSystem, errorReporter));
}
+ void _computeWarnings(
+ FileState file,
+ CompilationUnit unit, {
+ required List<UsedImportedElements> usedImportedElements,
+ required UsedLocalElements usedElements,
+ }) {
+ AnalysisErrorListener errorListener = _getErrorListener(file);
+ ErrorReporter errorReporter = _getErrorReporter(file);
+
+ if (!_libraryElement.isNonNullableByDefault) {
+ unit.accept(
+ LegacyDeadCodeVerifier(
+ errorReporter,
+ typeSystem: _typeSystem,
+ ),
+ );
+ }
+
+ UnicodeTextVerifier(errorReporter).verify(unit, file.content);
+
+ unit.accept(DeadCodeVerifier(errorReporter));
+
+ unit.accept(
+ BestPracticesVerifier(
+ errorReporter,
+ _typeProvider,
+ _libraryElement,
+ unit,
+ file.content,
+ declaredVariables: _declaredVariables,
+ typeSystem: _typeSystem,
+ inheritanceManager: _inheritance,
+ analysisOptions: _analysisOptions,
+ workspacePackage: _library.file.workspacePackage,
+ ),
+ );
+
+ unit.accept(OverrideVerifier(
+ _inheritance,
+ _libraryElement,
+ errorReporter,
+ ));
+
+ TodoFinder(errorReporter).findIn(unit);
+ LanguageVersionOverrideVerifier(errorReporter).verify(unit);
+
+ // Verify imports.
+ {
+ ImportsVerifier verifier = ImportsVerifier();
+ verifier.addImports(unit);
+ usedImportedElements.forEach(verifier.removeUsedElements);
+ verifier.generateDuplicateExportWarnings(errorReporter);
+ verifier.generateDuplicateImportWarnings(errorReporter);
+ verifier.generateDuplicateShownHiddenNameWarnings(errorReporter);
+ verifier.generateUnusedImportHints(errorReporter);
+ verifier.generateUnusedShownNameHints(errorReporter);
+ verifier.generateUnnecessaryImportHints(
+ errorReporter, usedImportedElements);
+ }
+
+ // Unused local elements.
+ {
+ UnusedLocalElementsVerifier visitor = UnusedLocalElementsVerifier(
+ errorListener, usedElements, _inheritance, _libraryElement);
+ unit.accept(visitor);
+ }
+
+ //
+ // Find code that uses features from an SDK version that does not satisfy
+ // the SDK constraints specified in analysis options.
+ //
+ var sdkVersionConstraint = _analysisOptions.sdkVersionConstraint;
+ if (sdkVersionConstraint != null) {
+ SdkConstraintVerifier verifier = SdkConstraintVerifier(
+ errorReporter, _libraryElement, _typeProvider, sdkVersionConstraint);
+ unit.accept(verifier);
+ }
+ }
+
/// Return a subset of the given [errors] that are not marked as ignored in
/// the [file].
List<AnalysisError> _filterIgnoredErrors(
diff --git a/analyzer/lib/src/dart/analysis/search.dart b/analyzer/lib/src/dart/analysis/search.dart
index 80f9029..1365e1b 100644
--- a/analyzer/lib/src/dart/analysis/search.dart
+++ b/analyzer/lib/src/dart/analysis/search.dart
@@ -738,6 +738,11 @@
PatternVariableElementImpl element,
SearchedFiles searchedFiles,
) async {
+ String path = element.source.fullName;
+ if (!searchedFiles.add(path, this)) {
+ return const <SearchResult>[];
+ }
+
var rootVariable = element.rootVariable;
var transitiveVariables = rootVariable is JoinPatternVariableElementImpl
? rootVariable.transitiveVariables
diff --git a/analyzer/lib/src/dart/analysis/unlinked_unit_store.dart b/analyzer/lib/src/dart/analysis/unlinked_unit_store.dart
index c6eb2ca..24c30f1 100644
--- a/analyzer/lib/src/dart/analysis/unlinked_unit_store.dart
+++ b/analyzer/lib/src/dart/analysis/unlinked_unit_store.dart
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
import 'package:analyzer/src/dart/analysis/unlinked_data.dart';
+import 'package:meta/meta.dart';
abstract class UnlinkedUnitStore {
void clear();
@@ -14,16 +15,17 @@
class UnlinkedUnitStoreImpl implements UnlinkedUnitStore {
// TODO(jensj): Could we use finalizers and automatically clean up
// this map?
- final Map<String, _UnlinkedUnitStoreData> _deserializedUnlinked = {};
+ @visibleForTesting
+ final Map<String, _UnlinkedUnitStoreData> map = {};
@override
void clear() {
- _deserializedUnlinked.clear();
+ map.clear();
}
@override
AnalysisDriverUnlinkedUnit? get(String key) {
- var lookup = _deserializedUnlinked[key];
+ var lookup = map[key];
if (lookup != null) {
lookup.usageCount++;
return lookup.value.target;
@@ -33,16 +35,16 @@
@override
void put(String key, AnalysisDriverUnlinkedUnit value) {
- _deserializedUnlinked[key] = _UnlinkedUnitStoreData(WeakReference(value));
+ map[key] = _UnlinkedUnitStoreData(WeakReference(value));
}
@override
void release(String key) {
- var lookup = _deserializedUnlinked[key];
+ var lookup = map[key];
if (lookup != null) {
lookup.usageCount--;
if (lookup.usageCount <= 0) {
- _deserializedUnlinked.remove(key);
+ map.remove(key);
}
}
}
diff --git a/analyzer/lib/src/dart/ast/ast.dart b/analyzer/lib/src/dart/ast/ast.dart
index 954d50e..5cae50f 100644
--- a/analyzer/lib/src/dart/ast/ast.dart
+++ b/analyzer/lib/src/dart/ast/ast.dart
@@ -1848,6 +1848,7 @@
final Token? macroKeyword;
/// The 'inline' keyword, or `null` if the keyword was absent.
+ @override
final Token? inlineKeyword;
/// The 'sealed' keyword, or `null` if the keyword was absent.
@@ -2859,7 +2860,7 @@
}
@override
- Token get beginToken => expression.beginToken;
+ Token get beginToken => constKeyword ?? expression.beginToken;
@override
Token get endToken => expression.endToken;
@@ -3439,9 +3440,10 @@
/// | [LogicalAndPattern]
/// | [LogicalOrPattern]
/// | [MapPattern]
+/// | [NullAssertPattern]
+/// | [NullCheckPattern]
/// | [ObjectPattern]
/// | [ParenthesizedPattern]
-/// | [PostfixPattern]
/// | [RecordPattern]
/// | [RelationalPattern]
@experimental
@@ -3662,12 +3664,10 @@
ResolverVisitor resolverVisitor,
SharedMatchContext context,
) {
- declaredElement!.type = resolverVisitor.analyzeDeclaredVariablePattern(
- context,
- this,
- declaredElement!,
- declaredElement!.name,
- type?.typeOrThrow);
+ declaredElement!.type = resolverVisitor
+ .analyzeDeclaredVariablePattern(context, this, declaredElement!,
+ declaredElement!.name, type?.typeOrThrow)
+ .staticType;
}
@override
@@ -4482,7 +4482,7 @@
if (parent is ConstantContextForExpressionImpl) {
return true;
} else if (parent is ConstantPatternImpl) {
- return true;
+ return parent.constKeyword != null;
} else if (parent is EnumConstantArguments) {
return true;
} else if (parent is TypedLiteralImpl && parent.constKeyword != null) {
@@ -5847,6 +5847,7 @@
@override
Token? get star => null;
+ @Deprecated('Not used by clients')
@override
bool isPotentiallyMutatedInClosure(VariableElement variable) {
if (localVariableInfo == null) {
@@ -8390,6 +8391,7 @@
/// The set of local variables and parameters that are potentially mutated
/// within a local function other than the function in which they are
/// declared.
+ @Deprecated('Not used by clients')
final Set<VariableElement> potentiallyMutatedInClosure = <VariableElement>{};
/// The set of local variables and parameters that are potentially mutated
@@ -10456,6 +10458,16 @@
Token get beginToken => name?.beginToken ?? pattern.beginToken;
@override
+ String? get effectiveName {
+ final nameNode = name;
+ if (nameNode != null) {
+ final nameToken = nameNode.name ?? pattern.variablePattern?.name;
+ return nameToken?.lexeme;
+ }
+ return null;
+ }
+
+ @override
Token get endToken => pattern.endToken;
@override
@@ -12911,10 +12923,12 @@
@override
void resolveExpression(ResolverVisitor resolver, DartType contextType) {
+ var previousExhaustiveness = resolver.legacySwitchExhaustiveness;
staticType = resolver
.analyzeSwitchExpression(this, expression, cases.length, contextType)
.type;
resolver.popRewrite();
+ resolver.legacySwitchExhaustiveness = previousExhaustiveness;
}
@override
diff --git a/analyzer/lib/src/dart/ast/extensions.dart b/analyzer/lib/src/dart/ast/extensions.dart
index 997ccdb..77b9ab9 100644
--- a/analyzer/lib/src/dart/ast/extensions.dart
+++ b/analyzer/lib/src/dart/ast/extensions.dart
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/syntactic_entity.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
@@ -165,6 +166,15 @@
return _readElement(this);
}
+ SimpleIdentifier get simpleName {
+ final self = this;
+ if (self is SimpleIdentifier) {
+ return self;
+ } else {
+ return (self as PrefixedIdentifier).identifier;
+ }
+ }
+
Element? get writeElement {
return _writeElement(this);
}
@@ -191,6 +201,25 @@
}
}
+extension PatternFieldImplExtension on PatternFieldImpl {
+ /// A [SyntacticEntity] which can be used in error reporting, which is valid
+ /// for both explicit getter names (like `Rect(width: var w, height: var h)`)
+ /// and implicit getter names (like `Rect(:var width, :var height)`).
+ SyntacticEntity get errorEntity {
+ var fieldName = name;
+ if (fieldName == null) {
+ return this;
+ }
+ var fieldNameName = fieldName.name;
+ if (fieldNameName == null) {
+ var variablePattern = pattern.variablePattern;
+ return variablePattern?.name ?? this;
+ } else {
+ return fieldNameName;
+ }
+ }
+}
+
extension RecordTypeAnnotationExtension on RecordTypeAnnotation {
List<RecordTypeAnnotationField> get fields {
return [
diff --git a/analyzer/lib/src/dart/constant/constant_verifier.dart b/analyzer/lib/src/dart/constant/constant_verifier.dart
index 567a756..fc4ba4e 100644
--- a/analyzer/lib/src/dart/constant/constant_verifier.dart
+++ b/analyzer/lib/src/dart/constant/constant_verifier.dart
@@ -23,6 +23,7 @@
import 'package:analyzer/src/dart/constant/potentially_constant.dart';
import 'package:analyzer/src/dart/constant/value.dart';
import 'package:analyzer/src/dart/element/element.dart';
+import 'package:analyzer/src/dart/element/least_greatest_closure.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/dart/element/type_system.dart';
import 'package:analyzer/src/diagnostic/diagnostic_factory.dart';
@@ -63,7 +64,9 @@
/// the exhaustiveness of the switch has been checked.
Map<ConstantPattern, DartObjectImpl>? _constantPatternValues;
- final ExhaustivenessDataForTesting? exhaustivenessDataForTesting;
+ Map<Expression, DartObjectImpl>? _mapPatternKeyValues;
+
+ late final ExhaustivenessDataForTesting? exhaustivenessDataForTesting;
/// Initialize a newly created constant verifier.
ConstantVerifier(ErrorReporter errorReporter,
@@ -91,9 +94,11 @@
_currentLibrary.featureSet.isEnabled(Feature.non_nullable),
configuration: ConstantEvaluationConfiguration(),
),
- _exhaustivenessCache = AnalyzerExhaustivenessCache(_typeSystem),
- exhaustivenessDataForTesting =
- retainDataForTesting ? ExhaustivenessDataForTesting() : null;
+ _exhaustivenessCache = AnalyzerExhaustivenessCache(_typeSystem) {
+ exhaustivenessDataForTesting = retainDataForTesting
+ ? ExhaustivenessDataForTesting(_exhaustivenessCache)
+ : null;
+ }
@override
void visitAnnotation(Annotation node) {
@@ -297,6 +302,7 @@
CompileTimeErrorCode.NON_CONSTANT_MAP_PATTERN_KEY,
);
if (keyValue != null) {
+ _mapPatternKeyValues?[key] = keyValue;
var existingKey = uniqueKeys[keyValue];
if (existingKey != null) {
duplicateKeys[key] = existingKey;
@@ -387,21 +393,23 @@
@override
void visitSwitchExpression(SwitchExpression node) {
- _withConstantPatternValues((constantPatternValues) {
+ _withConstantPatternValues((mapPatternKeyValues, constantPatternValues) {
super.visitSwitchExpression(node);
_validateSwitchExhaustiveness(
node: node,
switchKeyword: node.switchKeyword,
scrutinee: node.expression,
caseNodes: node.cases,
+ mapPatternKeyValues: mapPatternKeyValues,
constantPatternValues: constantPatternValues,
+ mustBeExhaustive: true,
);
});
}
@override
void visitSwitchStatement(SwitchStatement node) {
- _withConstantPatternValues((constantPatternValues) {
+ _withConstantPatternValues((mapPatternKeyValues, constantPatternValues) {
super.visitSwitchStatement(node);
if (_currentLibrary.featureSet.isEnabled(Feature.patterns)) {
_validateSwitchExhaustiveness(
@@ -409,7 +417,10 @@
switchKeyword: node.switchKeyword,
scrutinee: node.expression,
caseNodes: node.members,
+ mapPatternKeyValues: mapPatternKeyValues,
constantPatternValues: constantPatternValues,
+ mustBeExhaustive:
+ _typeSystem.isAlwaysExhaustive(node.expression.typeOrThrow),
);
} else if (_currentLibrary.isNonNullableByDefault) {
_validateSwitchStatement_nullSafety(node);
@@ -447,15 +458,23 @@
/// `false`, taking into account the fact that [constantType] has primitive
/// equality.
bool _canBeEqual(DartType constantType, DartType valueType) {
- if (constantType is InterfaceType && constantType.typeArguments.isEmpty) {
+ if (constantType is InterfaceType) {
if (valueType is InterfaceType) {
- return valueType.typeArguments.isEmpty &&
- _typeSystem.isSubtypeOf(constantType, valueType);
+ if (constantType.isDartCoreInt && valueType.isDartCoreDouble) {
+ return true;
+ }
+ final valueTypeGreatest = PatternGreatestClosureHelper(
+ topType: _typeSystem.objectQuestion,
+ bottomType: NeverTypeImpl.instance,
+ ).eliminateToGreatest(valueType);
+ return _typeSystem.isSubtypeOf(constantType, valueTypeGreatest);
} else if (valueType is TypeParameterTypeImpl) {
final bound = valueType.promotedBound ?? valueType.element.bound;
if (bound != null && !hasTypeParameterReference(bound)) {
return _canBeEqual(constantType, bound);
}
+ } else if (valueType is FunctionType) {
+ return false;
}
}
// All other cases are not supported, so no warning.
@@ -746,7 +765,9 @@
required Token switchKeyword,
required Expression scrutinee,
required List<AstNode> caseNodes,
+ required Map<Expression, DartObjectImpl> mapPatternKeyValues,
required Map<ConstantPattern, DartObjectImpl> constantPatternValues,
+ required bool mustBeExhaustive,
}) {
final scrutineeType = scrutinee.typeOrThrow;
final scrutineeTypeEx = _exhaustivenessCache.getStaticType(scrutineeType);
@@ -756,6 +777,11 @@
var hasDefault = false;
// Build spaces for cases.
+ final patternConverter = PatternConverter(
+ cache: _exhaustivenessCache,
+ mapPatternKeyValues: mapPatternKeyValues,
+ constantPatternValues: constantPatternValues,
+ );
for (final caseNode in caseNodes) {
GuardedPattern? guardedPattern;
if (caseNode is SwitchDefault) {
@@ -769,29 +795,21 @@
}
if (guardedPattern != null) {
- Space space;
- if (guardedPattern.whenClause != null) {
- // TODO(johnniwinther): Test this.
- space = Space(_exhaustivenessCache.getUnknownStaticType());
- } else {
- final pattern = guardedPattern.pattern;
- space = convertPatternToSpace(
- _exhaustivenessCache, pattern, constantPatternValues);
- }
+ Space space = patternConverter.createRootSpace(
+ scrutineeTypeEx, guardedPattern.pattern,
+ hasGuard: guardedPattern.whenClause != null);
caseNodesWithSpace.add(caseNode);
caseSpaces.add(space);
}
}
// Prepare for recording data for testing.
- List<Space>? remainingSpaces;
final exhaustivenessDataForTesting = this.exhaustivenessDataForTesting;
- if (exhaustivenessDataForTesting != null) {
- remainingSpaces = [];
- }
// Compute and report errors.
- final errors = reportErrors(scrutineeTypeEx, caseSpaces, remainingSpaces);
+ final errors =
+ reportErrors(_exhaustivenessCache, scrutineeTypeEx, caseSpaces);
+ final reportNonExhaustive = mustBeExhaustive && !hasDefault;
for (final error in errors) {
if (error is UnreachableCaseError) {
final caseNode = caseNodesWithSpace[error.index];
@@ -807,39 +825,28 @@
HintCode.UNREACHABLE_SWITCH_CASE,
errorToken,
);
- } else if (error is NonExhaustiveError &&
- _typeSystem.isAlwaysExhaustive(scrutineeType) &&
- !hasDefault) {
+ } else if (error is NonExhaustiveError && reportNonExhaustive) {
_errorReporter.reportErrorForToken(
CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH,
switchKeyword,
- [scrutineeType, error.witness],
+ [scrutineeType, error.witness.toString()],
);
}
}
// Record data for testing.
- if (exhaustivenessDataForTesting != null && remainingSpaces != null) {
- assert(remainingSpaces.isEmpty ||
- remainingSpaces.length == caseSpaces.length + 1);
+ if (exhaustivenessDataForTesting != null) {
for (var i = 0; i < caseSpaces.length; i++) {
final caseNode = caseNodesWithSpace[i];
exhaustivenessDataForTesting.caseSpaces[caseNode] = caseSpaces[i];
- if (remainingSpaces.isNotEmpty) {
- exhaustivenessDataForTesting.remainingSpaces[caseNode] =
- remainingSpaces[i];
- }
}
exhaustivenessDataForTesting.switchScrutineeType[node] = scrutineeTypeEx;
- if (remainingSpaces.isNotEmpty) {
- exhaustivenessDataForTesting.remainingSpaces[node] =
- remainingSpaces.last;
- }
+ exhaustivenessDataForTesting.switchCases[node] = caseSpaces;
for (var error in errors) {
if (error is UnreachableCaseError) {
exhaustivenessDataForTesting.errors[caseNodesWithSpace[error.index]] =
error;
- } else {
+ } else if (reportNonExhaustive) {
exhaustivenessDataForTesting.errors[node] = error;
}
}
@@ -942,12 +949,17 @@
/// Runs [f] with new [_constantPatternValues].
void _withConstantPatternValues(
- void Function(Map<ConstantPattern, DartObjectImpl> constantPatternValues) f,
+ void Function(Map<Expression, DartObjectImpl> mapPatternKeyValues,
+ Map<ConstantPattern, DartObjectImpl> constantPatternValues)
+ f,
) {
- final previous = _constantPatternValues;
- final values = _constantPatternValues = {};
- f(values);
- _constantPatternValues = previous;
+ final previousMapKeyValues = _mapPatternKeyValues;
+ final previousConstantPatternValues = _constantPatternValues;
+ final mapKeyValues = _mapPatternKeyValues = {};
+ final constantValues = _constantPatternValues = {};
+ f(mapKeyValues, constantValues);
+ _mapPatternKeyValues = previousMapKeyValues;
+ _constantPatternValues = previousConstantPatternValues;
}
}
diff --git a/analyzer/lib/src/dart/constant/evaluation.dart b/analyzer/lib/src/dart/constant/evaluation.dart
index da7f44d..541081e 100644
--- a/analyzer/lib/src/dart/constant/evaluation.dart
+++ b/analyzer/lib/src/dart/constant/evaluation.dart
@@ -32,21 +32,29 @@
import 'package:analyzer/src/utilities/extensions/collection.dart';
class ConstantEvaluationConfiguration {
- /// During evaluation of enum constants we might need to report an error
- /// that is associated with the [InstanceCreationExpression], but this
- /// expression is synthetic. Instead, we remember the corresponding
- /// [EnumConstantDeclaration] and report the error on it.
- final Map<Expression, EnumConstantDeclaration> _enumConstants = {};
+ final Map<AstNode, AstNode> _errorNodes = {};
- void addEnumConstant({
- required EnumConstantDeclaration declaration,
- required Expression initializer,
+ /// We evaluate constant values using expressions stored in elements.
+ /// But these expressions don't have offsets set.
+ /// This includes elements and expressions of the file being resolved.
+ /// So, to make sure that we report errors at right offsets, we "replace"
+ /// these constant expressions.
+ ///
+ /// A similar issue happens for enum values, which are desugared into
+ /// synthetic [InstanceCreationExpression], which never had any offsets.
+ /// So, we remember that any errors should be reported at the corresponding
+ /// [EnumConstantDeclaration]s.
+ void addErrorNode({
+ required AstNode? fromElement,
+ required AstNode? fromAst,
}) {
- _enumConstants[initializer] = declaration;
+ if (fromElement != null && fromAst != null) {
+ _errorNodes[fromElement] = fromAst;
+ }
}
AstNode errorNode(AstNode node) {
- return _enumConstants[node] ?? node;
+ return _errorNodes[node] ?? node;
}
}
@@ -544,6 +552,7 @@
_substitution = substitution {
_dartObjectComputer = DartObjectComputer(
typeSystem,
+ _library.featureSet,
_errorReporter,
);
}
@@ -613,7 +622,7 @@
} else if (operatorType == TokenType.CARET) {
return _dartObjectComputer.eagerXor(node, leftResult, rightResult);
} else if (operatorType == TokenType.EQ_EQ) {
- return _dartObjectComputer.lazyEqualEqual(node, leftResult, rightResult);
+ return _dartObjectComputer.equalEqual(node, leftResult, rightResult);
} else if (operatorType == TokenType.GT) {
return _dartObjectComputer.greaterThan(node, leftResult, rightResult);
} else if (operatorType == TokenType.GT_EQ) {
@@ -927,6 +936,8 @@
if (!_isNonNullableByDefault && hasTypeParameterReference(type)) {
return super.visitNamedType(node);
+ } else {
+ node.name.accept(this);
}
if (_substitution != null) {
@@ -1417,7 +1428,8 @@
// TODO(https://github.com/dart-lang/sdk/issues/47061): Use a specific
// error code.
- _error(node, null);
+ final errorNode = evaluationEngine.configuration.errorNode(node);
+ _error(errorNode, null);
return null;
}
@@ -1566,11 +1578,12 @@
/// class and for collecting errors during evaluation.
class DartObjectComputer {
final TypeSystemImpl _typeSystem;
+ final FeatureSet _featureSet;
/// The error reporter that we are using to collect errors.
final ErrorReporter _errorReporter;
- DartObjectComputer(this._typeSystem, this._errorReporter);
+ DartObjectComputer(this._typeSystem, this._featureSet, this._errorReporter);
DartObjectImpl? add(BinaryExpression node, DartObjectImpl? leftOperand,
DartObjectImpl? rightOperand) {
@@ -1698,7 +1711,7 @@
DartObjectImpl? rightOperand) {
if (leftOperand != null && rightOperand != null) {
try {
- return leftOperand.equalEqual(_typeSystem, rightOperand);
+ return leftOperand.equalEqual(_typeSystem, _featureSet, rightOperand);
} on EvaluationException catch (exception) {
_errorReporter.reportErrorForNode(exception.errorCode, node);
}
@@ -1766,18 +1779,6 @@
return null;
}
- DartObjectImpl? lazyEqualEqual(Expression node, DartObjectImpl? leftOperand,
- DartObjectImpl? rightOperand) {
- if (leftOperand != null && rightOperand != null) {
- try {
- return leftOperand.lazyEqualEqual(_typeSystem, rightOperand);
- } on EvaluationException catch (exception) {
- _errorReporter.reportErrorForNode(exception.errorCode, node);
- }
- }
- return null;
- }
-
DartObjectImpl? lazyOr(BinaryExpression node, DartObjectImpl? leftOperand,
DartObjectImpl? Function() rightOperandComputer) {
if (leftOperand != null) {
@@ -1878,7 +1879,7 @@
DartObjectImpl? rightOperand) {
if (leftOperand != null && rightOperand != null) {
try {
- return leftOperand.notEqual(_typeSystem, rightOperand);
+ return leftOperand.notEqual(_typeSystem, _featureSet, rightOperand);
} on EvaluationException catch (exception) {
_errorReporter.reportErrorForNode(exception.errorCode, node);
}
diff --git a/analyzer/lib/src/dart/constant/utilities.dart b/analyzer/lib/src/dart/constant/utilities.dart
index 94c5bf1..a2be807 100644
--- a/analyzer/lib/src/dart/constant/utilities.dart
+++ b/analyzer/lib/src/dart/constant/utilities.dart
@@ -159,10 +159,9 @@
var element = node.declaredElement as ConstFieldElementImpl;
constantsToCompute.add(element);
- final initializer = element.constantInitializer!;
- configuration.addEnumConstant(
- declaration: node,
- initializer: initializer,
+ configuration.addErrorNode(
+ fromElement: element.constantInitializer,
+ fromAst: node,
);
}
@@ -178,6 +177,14 @@
node.isFinal &&
!element.isStatic)) {
constantsToCompute.add(element);
+ // Fill error nodes.
+ if (element is ConstVariableElement) {
+ final constElement = element as ConstVariableElement;
+ configuration.addErrorNode(
+ fromElement: constElement.constantInitializer,
+ fromAst: node.initializer,
+ );
+ }
}
}
}
diff --git a/analyzer/lib/src/dart/constant/value.dart b/analyzer/lib/src/dart/constant/value.dart
index 30f9d0d..658e609 100644
--- a/analyzer/lib/src/dart/constant/value.dart
+++ b/analyzer/lib/src/dart/constant/value.dart
@@ -66,7 +66,6 @@
@override
BoolState equalEqual(TypeSystemImpl typeSystem, InstanceState rightOperand) {
- assertBoolNumStringOrNull(rightOperand);
return isIdentical(typeSystem, rightOperand);
}
@@ -430,7 +429,10 @@
/// Throws an [EvaluationException] if the operator is not appropriate for an
/// object of this kind.
DartObjectImpl equalEqual(
- TypeSystemImpl typeSystem, DartObjectImpl rightOperand) {
+ TypeSystemImpl typeSystem,
+ FeatureSet featureSet,
+ DartObjectImpl rightOperand,
+ ) {
if (isNull || rightOperand.isNull) {
return DartObjectImpl(
typeSystem,
@@ -440,12 +442,22 @@
: BoolState.FALSE_STATE,
);
}
- if (isBoolNumStringOrNull) {
- return DartObjectImpl(
- typeSystem,
- typeSystem.typeProvider.boolType,
- state.equalEqual(typeSystem, rightOperand.state),
- );
+ if (featureSet.isEnabled(Feature.patterns)) {
+ if (state is DoubleState || hasPrimitiveEquality(featureSet)) {
+ return DartObjectImpl(
+ typeSystem,
+ typeSystem.typeProvider.boolType,
+ state.equalEqual(typeSystem, rightOperand.state),
+ );
+ }
+ } else {
+ if (isBoolNumStringOrNull) {
+ return DartObjectImpl(
+ typeSystem,
+ typeSystem.typeProvider.boolType,
+ state.equalEqual(typeSystem, rightOperand.state),
+ );
+ }
}
throw EvaluationException(
CompileTimeErrorCode.CONST_EVAL_TYPE_BOOL_NUM_STRING);
@@ -593,33 +605,6 @@
);
}
- /// Return the result of invoking the '==' operator on this object with the
- /// [rightOperand].
- ///
- /// Throws an [EvaluationException] if the operator is not appropriate for an
- /// object of this kind.
- DartObjectImpl lazyEqualEqual(
- TypeSystemImpl typeSystem, DartObjectImpl rightOperand) {
- if (isNull || rightOperand.isNull) {
- return DartObjectImpl(
- typeSystem,
- typeSystem.typeProvider.boolType,
- isNull && rightOperand.isNull
- ? BoolState.TRUE_STATE
- : BoolState.FALSE_STATE,
- );
- }
- if (isBoolNumStringOrNull) {
- return DartObjectImpl(
- typeSystem,
- typeSystem.typeProvider.boolType,
- state.lazyEqualEqual(typeSystem, rightOperand.state),
- );
- }
- throw EvaluationException(
- CompileTimeErrorCode.CONST_EVAL_TYPE_BOOL_NUM_STRING);
- }
-
/// Return the result of invoking the '||' operator on this object with the
/// [rightOperand].
///
@@ -740,8 +725,12 @@
/// Throws an [EvaluationException] if the operator is not appropriate for an
/// object of this kind.
DartObjectImpl notEqual(
- TypeSystemImpl typeSystem, DartObjectImpl rightOperand) {
- return equalEqual(typeSystem, rightOperand).logicalNot(typeSystem);
+ TypeSystemImpl typeSystem,
+ FeatureSet featureSet,
+ DartObjectImpl rightOperand,
+ ) {
+ return equalEqual(typeSystem, featureSet, rightOperand)
+ .logicalNot(typeSystem);
}
/// Return the result of converting this object to a 'String'.
@@ -1471,7 +1460,6 @@
@override
BoolState equalEqual(TypeSystemImpl typeSystem, InstanceState rightOperand) {
- assertBoolNumStringOrNull(rightOperand);
return isIdentical(typeSystem, rightOperand);
}
@@ -1576,18 +1564,6 @@
}
}
- /// Throw an exception if the given [state] does not represent a boolean,
- /// numeric, string or null value.
- void assertBoolNumStringOrNull(InstanceState state) {
- if (!(state is BoolState ||
- state is NumState ||
- state is StringState ||
- state is NullState)) {
- throw EvaluationException(
- CompileTimeErrorCode.CONST_EVAL_TYPE_BOOL_NUM_STRING);
- }
- }
-
/// Throw an exception if the given [state] does not represent an integer or
/// null value.
void assertIntOrNull(InstanceState state) {
@@ -1749,18 +1725,6 @@
return rightOperand!.convertToBool();
}
- /// Return the result of invoking the '==' operator on this object with the
- /// [rightOperand].
- ///
- /// Throws an [EvaluationException] if the operator is not appropriate for an
- /// object of this kind.
- BoolState lazyEqualEqual(
- TypeSystemImpl typeSystem,
- InstanceState rightOperand,
- ) {
- return isIdentical(typeSystem, rightOperand);
- }
-
/// Return the result of invoking the '||' operator on this object with the
/// [rightOperand].
///
@@ -2416,7 +2380,6 @@
@override
BoolState equalEqual(TypeSystemImpl typeSystem, InstanceState rightOperand) {
- assertBoolNumStringOrNull(rightOperand);
return isIdentical(typeSystem, rightOperand);
}
@@ -2494,7 +2457,6 @@
@override
BoolState equalEqual(TypeSystemImpl typeSystem, InstanceState rightOperand) {
- assertBoolNumStringOrNull(rightOperand);
return isIdentical(typeSystem, rightOperand);
}
@@ -2556,7 +2518,6 @@
@override
BoolState equalEqual(TypeSystemImpl typeSystem, InstanceState rightOperand) {
- assertBoolNumStringOrNull(rightOperand);
return isIdentical(typeSystem, rightOperand);
}
@@ -2584,7 +2545,6 @@
@override
BoolState equalEqual(TypeSystemImpl typeSystem, InstanceState rightOperand) {
- assertBoolNumStringOrNull(rightOperand);
return isIdentical(typeSystem, rightOperand);
}
}
@@ -2761,7 +2721,6 @@
@override
BoolState equalEqual(TypeSystemImpl typeSystem, InstanceState rightOperand) {
- assertBoolNumStringOrNull(rightOperand);
return isIdentical(typeSystem, rightOperand);
}
@@ -2838,7 +2797,6 @@
@override
BoolState equalEqual(TypeSystemImpl typeSystem, InstanceState rightOperand) {
- assertBoolNumStringOrNull(rightOperand);
return isIdentical(typeSystem, rightOperand);
}
@@ -2900,7 +2858,6 @@
@override
BoolState equalEqual(TypeSystemImpl typeSystem, InstanceState rightOperand) {
- assertBoolNumStringOrNull(rightOperand);
return isIdentical(typeSystem, rightOperand);
}
@@ -2954,7 +2911,6 @@
@override
BoolState equalEqual(TypeSystemImpl typeSystem, InstanceState rightOperand) {
- assertBoolNumStringOrNull(rightOperand);
return isIdentical(typeSystem, rightOperand);
}
diff --git a/analyzer/lib/src/dart/element/element.dart b/analyzer/lib/src/dart/element/element.dart
index c932e6a..3a93481 100644
--- a/analyzer/lib/src/dart/element/element.dart
+++ b/analyzer/lib/src/dart/element/element.dart
@@ -5,6 +5,8 @@
import 'dart:collection';
import 'package:_fe_analyzer_shared/src/scanner/string_canonicalizer.dart';
+import 'package:_fe_analyzer_shared/src/type_inference/type_analyzer.dart'
+ as shared;
import 'package:analyzer/dart/analysis/features.dart';
import 'package:analyzer/dart/analysis/session.dart';
import 'package:analyzer/dart/ast/ast.dart';
@@ -28,6 +30,7 @@
import 'package:analyzer/src/dart/element/member.dart';
import 'package:analyzer/src/dart/element/nullability_eliminator.dart';
import 'package:analyzer/src/dart/element/scope.dart';
+import 'package:analyzer/src/dart/element/since_sdk_version.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/dart/element/type_algebra.dart';
import 'package:analyzer/src/dart/element/type_provider.dart';
@@ -52,6 +55,7 @@
import 'package:analyzer/src/utilities/extensions/collection.dart';
import 'package:analyzer/src/utilities/extensions/string.dart';
import 'package:collection/collection.dart';
+import 'package:pub_semver/pub_semver.dart';
/// A concrete implementation of a [ClassElement].
abstract class AbstractClassElementImpl extends _ExistingElementImpl
@@ -570,6 +574,10 @@
implements BindPatternVariableElement {
final DeclaredVariablePatternImpl node;
+ /// This flag is set to `true` if this variable clashes with another
+ /// pattern variable with the same name within the same pattern.
+ bool isDuplicate = false;
+
BindPatternVariableElementImpl(this.node, super.name, super.offset);
}
@@ -690,6 +698,9 @@
}
@override
+ bool get isConstructable => !isSealed && !isAbstract;
+
+ @override
bool get isDartCoreEnum {
return name == 'Enum' && library.isDartCore;
}
@@ -840,7 +851,8 @@
/// one (this is used to detect cycles).
List<ConstructorElementImpl> _computeMixinAppConstructors(
[List<ClassElementImpl>? visitedClasses]) {
- if (supertype == null) {
+ final superType = supertype;
+ if (superType == null) {
// Shouldn't ever happen, since the only classes with no supertype are
// Object and mixins, and they aren't a mixin application. But for
// safety's sake just assume an empty list.
@@ -848,7 +860,7 @@
return <ConstructorElementImpl>[];
}
- var superElement = supertype!.element as ClassElementImpl;
+ final superElement = superType.element as ClassElementImpl;
// First get the list of constructors of the superclass which need to be
// forwarded to this class.
@@ -884,11 +896,11 @@
var superClassParameters = superElement.typeParameters;
List<DartType> argumentTypes = List<DartType>.filled(
superClassParameters.length, DynamicTypeImpl.instance);
- for (int i = 0; i < supertype!.typeArguments.length; i++) {
+ for (int i = 0; i < superType.typeArguments.length; i++) {
if (i >= argumentTypes.length) {
break;
}
- argumentTypes[i] = supertype!.typeArguments[i];
+ argumentTypes[i] = superType.typeArguments[i];
}
var substitution =
Substitution.fromPairs(superClassParameters, argumentTypes);
@@ -958,7 +970,7 @@
implicitConstructor.enclosingElement = this;
// TODO(scheglov) Why do we manually map parameters types above?
implicitConstructor.superConstructor =
- ConstructorMember.from(superclassConstructor, supertype!);
+ ConstructorMember.from(superclassConstructor, superType);
var isNamed = superclassConstructor.name.isNotEmpty;
implicitConstructor.constantInitializers = [
@@ -2059,7 +2071,7 @@
/// The AST of the annotation itself, cloned from the resolved AST for the
/// source code.
- late Annotation annotationAst;
+ late AnnotationImpl annotationAst;
/// The result of evaluating this annotation as a compile-time constant
/// expression, or `null` if the compilation unit containing the variable has
@@ -2083,6 +2095,15 @@
@override
bool get isConstantEvaluated => evaluationResult != null;
+ bool get isDartInternalSince {
+ final element = this.element;
+ if (element is ConstructorElement) {
+ return element.enclosingElement.name == 'Since' &&
+ element.library.source.uri.toString() == 'dart:_internal';
+ }
+ return false;
+ }
+
@override
bool get isDeprecated {
final element = this.element;
@@ -2258,6 +2279,14 @@
static const _metadataFlag_hasDeprecated = 1 << 1;
static const _metadataFlag_hasOverride = 1 << 2;
+ /// Cached values for [sinceSdkVersion].
+ ///
+ /// Only very few elements have `@Since()` annotations, so instead of adding
+ /// an instance field to [ElementImpl], we attach this information this way.
+ /// We ask it only when [Modifier.HAS_SINCE_SDK_VERSION_VALUE] is `true`, so
+ /// don't pay for a hash lookup when we know that the result is `null`.
+ static final Expando<Version> _sinceSdkVersion = Expando<Version>();
+
static int _NEXT_ID = 0;
@override
@@ -2712,6 +2741,22 @@
}
@override
+ Version? get sinceSdkVersion {
+ if (!hasModifier(Modifier.HAS_SINCE_SDK_VERSION_COMPUTED)) {
+ setModifier(Modifier.HAS_SINCE_SDK_VERSION_COMPUTED, true);
+ final result = SinceSdkVersionComputer().compute(this);
+ if (result != null) {
+ _sinceSdkVersion[this] = result;
+ setModifier(Modifier.HAS_SINCE_SDK_VERSION_VALUE, true);
+ }
+ }
+ if (hasModifier(Modifier.HAS_SINCE_SDK_VERSION_VALUE)) {
+ return _sinceSdkVersion[this];
+ }
+ return null;
+ }
+
+ @override
Source? get source {
return enclosingElement?.source;
}
@@ -3835,8 +3880,7 @@
@override
final List<PatternVariableElementImpl> variables;
- @override
- bool isConsistent;
+ shared.JoinedPatternVariableInconsistency inconsistency;
/// The identifiers that reference this element.
final List<SimpleIdentifier> references = [];
@@ -3845,7 +3889,7 @@
super.name,
super.offset,
this.variables,
- this.isConsistent,
+ this.inconsistency,
) {
for (var component in variables) {
component.join = this;
@@ -3855,6 +3899,11 @@
@override
int get hashCode => identityHashCode(this);
+ @override
+ bool get isConsistent {
+ return inconsistency == shared.JoinedPatternVariableInconsistency.none;
+ }
+
/// Returns this variable, and variables that join into it.
List<PatternVariableElementImpl> get transitiveVariables {
var result = <PatternVariableElementImpl>[];
@@ -4617,6 +4666,7 @@
definingCompilationUnit,
...libraryExports,
...libraryImports,
+ ...augmentationImports,
];
@override
@@ -4976,58 +5026,66 @@
static const Modifier HAS_PART_OF_DIRECTIVE =
Modifier('HAS_PART_OF_DIRECTIVE', 15);
+ /// Indicates that the value of [Element.sinceSdkVersion] was computed.
+ static const Modifier HAS_SINCE_SDK_VERSION_COMPUTED =
+ Modifier('HAS_SINCE_SDK_VERSION_COMPUTED', 16);
+
+ /// [HAS_SINCE_SDK_VERSION_COMPUTED] and the value was not `null`.
+ static const Modifier HAS_SINCE_SDK_VERSION_VALUE =
+ Modifier('HAS_SINCE_SDK_VERSION_VALUE', 17);
+
/// Indicates that the associated element did not have an explicit type
/// associated with it. If the element is an [ExecutableElement], then the
/// type being referred to is the return type.
- static const Modifier IMPLICIT_TYPE = Modifier('IMPLICIT_TYPE', 16);
+ static const Modifier IMPLICIT_TYPE = Modifier('IMPLICIT_TYPE', 18);
/// Indicates that the modifier 'interface' was applied to the element.
- static const Modifier INTERFACE = Modifier('INTERFACE', 17);
+ static const Modifier INTERFACE = Modifier('INTERFACE', 19);
/// Indicates that the method invokes the super method with the same name.
- static const Modifier INVOKES_SUPER_SELF = Modifier('INVOKES_SUPER_SELF', 18);
+ static const Modifier INVOKES_SUPER_SELF = Modifier('INVOKES_SUPER_SELF', 20);
/// Indicates that modifier 'lazy' was applied to the element.
- static const Modifier LATE = Modifier('LATE', 19);
+ static const Modifier LATE = Modifier('LATE', 21);
/// Indicates that a class is a macro builder.
- static const Modifier MACRO = Modifier('MACRO', 20);
+ static const Modifier MACRO = Modifier('MACRO', 22);
/// Indicates that a class is a mixin application.
- static const Modifier MIXIN_APPLICATION = Modifier('MIXIN_APPLICATION', 21);
+ static const Modifier MIXIN_APPLICATION = Modifier('MIXIN_APPLICATION', 23);
/// Indicates that a class is a mixin class.
- static const Modifier MIXIN_CLASS = Modifier('MIXIN_CLASS', 22);
+ static const Modifier MIXIN_CLASS = Modifier('MIXIN_CLASS', 24);
- static const Modifier PROMOTABLE = Modifier('IS_PROMOTABLE', 23);
+ static const Modifier PROMOTABLE = Modifier('IS_PROMOTABLE', 25);
/// Indicates whether the type of a [PropertyInducingElementImpl] should be
/// used to infer the initializer. We set it to `false` if the type was
/// inferred from the initializer itself.
static const Modifier SHOULD_USE_TYPE_FOR_INITIALIZER_INFERENCE =
- Modifier('SHOULD_USE_TYPE_FOR_INITIALIZER_INFERENCE', 24);
+ Modifier('SHOULD_USE_TYPE_FOR_INITIALIZER_INFERENCE', 26);
/// Indicates that the modifier 'sealed' was applied to the element.
- static const Modifier SEALED = Modifier('SEALED', 25);
+ static const Modifier SEALED = Modifier('SEALED', 27);
/// Indicates that the pseudo-modifier 'set' was applied to the element.
- static const Modifier SETTER = Modifier('SETTER', 26);
+ static const Modifier SETTER = Modifier('SETTER', 28);
/// See [TypeParameterizedElement.isSimplyBounded].
- static const Modifier SIMPLY_BOUNDED = Modifier('SIMPLY_BOUNDED', 27);
+ static const Modifier SIMPLY_BOUNDED = Modifier('SIMPLY_BOUNDED', 29);
/// Indicates that the modifier 'static' was applied to the element.
- static const Modifier STATIC = Modifier('STATIC', 28);
+ static const Modifier STATIC = Modifier('STATIC', 30);
/// Indicates that the element does not appear in the source code but was
/// implicitly created. For example, if a class does not define any
/// constructors, an implicit zero-argument constructor will be created and it
/// will be marked as being synthetic.
- static const Modifier SYNTHETIC = Modifier('SYNTHETIC', 29);
+ static const Modifier SYNTHETIC = Modifier('SYNTHETIC', 31);
/// Indicates that the element was appended to this enclosing element to
/// simulate temporary the effect of applying augmentation.
- static const Modifier TEMP_AUGMENTATION = Modifier('TEMP_AUGMENTATION', 30);
+ static const Modifier TEMP_AUGMENTATION = Modifier('TEMP_AUGMENTATION', 32);
static const List<Modifier> values = [
ABSTRACT,
@@ -5046,6 +5104,8 @@
GETTER,
HAS_INITIALIZER,
HAS_PART_OF_DIRECTIVE,
+ HAS_SINCE_SDK_VERSION_COMPUTED,
+ HAS_SINCE_SDK_VERSION_VALUE,
IMPLICIT_TYPE,
INTERFACE,
INVOKES_SUPER_SELF,
@@ -5228,6 +5288,9 @@
Element get nonSynthetic => this;
@override
+ Version? get sinceSdkVersion => null;
+
+ @override
Source? get source => null;
@override
@@ -5877,6 +5940,9 @@
DartType get returnTypeInternal => variable.type;
@override
+ Version? get sinceSdkVersion => variable.sinceSdkVersion;
+
+ @override
FunctionType get type => ElementTypeProvider.current.getExecutableType(this);
@override
@@ -5947,6 +6013,9 @@
DartType get returnTypeInternal => VoidTypeImpl.instance;
@override
+ Version? get sinceSdkVersion => variable.sinceSdkVersion;
+
+ @override
FunctionType get type => ElementTypeProvider.current.getExecutableType(this);
@override
diff --git a/analyzer/lib/src/dart/element/least_greatest_closure.dart b/analyzer/lib/src/dart/element/least_greatest_closure.dart
index d96fd08..5fcc6e8 100644
--- a/analyzer/lib/src/dart/element/least_greatest_closure.dart
+++ b/analyzer/lib/src/dart/element/least_greatest_closure.dart
@@ -94,3 +94,36 @@
return super.visitTypeParameterType(type);
}
}
+
+class PatternGreatestClosureHelper extends ReplacementVisitor {
+ final TypeImpl topType;
+ final TypeImpl bottomType;
+ bool _isCovariant = true;
+
+ PatternGreatestClosureHelper({
+ required this.topType,
+ required this.bottomType,
+ });
+
+ @override
+ void changeVariance() {
+ _isCovariant = !_isCovariant;
+ }
+
+ /// Returns a supertype of [type] for all values of type parameters.
+ DartType eliminateToGreatest(DartType type) {
+ _isCovariant = true;
+ return type.accept(this) ?? type;
+ }
+
+ @override
+ DartType? visitTypeParameterType(TypeParameterType type) {
+ final replacement = _isCovariant ? topType : bottomType;
+ return replacement.withNullability(
+ uniteNullabilities(
+ replacement.nullabilitySuffix,
+ type.nullabilitySuffix,
+ ),
+ );
+ }
+}
diff --git a/analyzer/lib/src/dart/element/member.dart b/analyzer/lib/src/dart/element/member.dart
index a537763..10997ba 100644
--- a/analyzer/lib/src/dart/element/member.dart
+++ b/analyzer/lib/src/dart/element/member.dart
@@ -15,6 +15,7 @@
import 'package:analyzer/src/generated/engine.dart' show AnalysisContext;
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/generated/utilities_dart.dart';
+import 'package:pub_semver/pub_semver.dart';
/// A constructor element defined in a parameterized type where the values of
/// the type parameters are known.
@@ -632,6 +633,9 @@
@override
AnalysisSession? get session => _declaration.session;
+ @override
+ Version? get sinceSdkVersion => _declaration.sinceSdkVersion;
+
/// The substitution for type parameters referenced in the base element.
MapSubstitution get substitution => _substitution;
diff --git a/analyzer/lib/src/dart/element/since_sdk_version.dart b/analyzer/lib/src/dart/element/since_sdk_version.dart
new file mode 100644
index 0000000..d998ef1
--- /dev/null
+++ b/analyzer/lib/src/dart/element/since_sdk_version.dart
@@ -0,0 +1,86 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/src/dart/ast/ast.dart';
+import 'package:analyzer/src/dart/element/element.dart';
+import 'package:collection/collection.dart';
+import 'package:pub_semver/pub_semver.dart';
+
+class SinceSdkVersionComputer {
+ static final RegExp _asLanguageVersion = RegExp(r'^\d+\.\d+$');
+
+ /// The [element] is a `dart:xyz` library, so it can have `@Since` annotations.
+ /// Evaluates its annotations and returns the version.
+ Version? compute(ElementImpl element) {
+ // Must be in a `dart:` library.
+ final librarySource = element.librarySource;
+ if (librarySource == null || !librarySource.uri.isScheme('dart')) {
+ return null;
+ }
+
+ // Fields cannot be referenced outside.
+ if (element is FieldElementImpl && element.isSynthetic) {
+ return null;
+ }
+
+ // We cannot add required parameters.
+ if (element is ParameterElementImpl && element.isRequired) {
+ return null;
+ }
+
+ final specified = _specifiedVersion(element);
+ final enclosing = element.enclosingElement?.sinceSdkVersion;
+ return specified.maxWith(enclosing);
+ }
+
+ /// Returns the parsed [Version], or `null` if wrong format.
+ static Version? _parseVersion(String versionStr) {
+ // 2.15
+ if (_asLanguageVersion.hasMatch(versionStr)) {
+ return Version.parse('$versionStr.0');
+ }
+
+ // 2.19.3 or 3.0.0-dev.4
+ try {
+ return Version.parse(versionStr);
+ } on FormatException {
+ return null;
+ }
+ }
+
+ /// Returns the maximal specified `@Since()` version, `null` if none.
+ static Version? _specifiedVersion(ElementImpl element) {
+ Version? result;
+ for (final annotation in element.metadata) {
+ annotation as ElementAnnotationImpl;
+ if (annotation.isDartInternalSince) {
+ final arguments = annotation.annotationAst.arguments?.arguments;
+ final versionNode = arguments?.singleOrNull;
+ if (versionNode is SimpleStringLiteralImpl) {
+ final versionStr = versionNode.value;
+ final version = _parseVersion(versionStr);
+ if (version != null) {
+ result = result.maxWith(version);
+ }
+ }
+ }
+ }
+ return result;
+ }
+}
+
+extension on Version? {
+ Version? maxWith(Version? other) {
+ final self = this;
+ if (self == null) {
+ return other;
+ } else if (other == null) {
+ return self;
+ } else if (self >= other) {
+ return self;
+ } else {
+ return other;
+ }
+ }
+}
diff --git a/analyzer/lib/src/dart/element/type.dart b/analyzer/lib/src/dart/element/type.dart
index 671cf20..5dd7288 100644
--- a/analyzer/lib/src/dart/element/type.dart
+++ b/analyzer/lib/src/dart/element/type.dart
@@ -623,6 +623,11 @@
}
@override
+ bool get isDartCoreType {
+ return element.name == "Type" && element.library.isDartCore;
+ }
+
+ @override
List<MethodElement> get methods {
if (_methods == null) {
List<MethodElement> methods = element.methods;
@@ -1233,6 +1238,9 @@
bool get isDartCoreSymbol => false;
@override
+ bool get isDartCoreType => false;
+
+ @override
bool get isDynamic => false;
@override
@@ -1462,6 +1470,7 @@
@override
int get hashCode => 2;
+ @Deprecated('Use `is VoidType` instead')
@override
bool get isVoid => true;
diff --git a/analyzer/lib/src/dart/element/type_system.dart b/analyzer/lib/src/dart/element/type_system.dart
index 36ac716..3adb6dc 100644
--- a/analyzer/lib/src/dart/element/type_system.dart
+++ b/analyzer/lib/src/dart/element/type_system.dart
@@ -33,6 +33,7 @@
import 'package:analyzer/src/dart/element/type_schema_elimination.dart';
import 'package:analyzer/src/dart/element/well_bounded.dart';
import 'package:analyzer/src/utilities/extensions/collection.dart';
+import 'package:meta/meta.dart';
/// Fresh type parameters created to unify two lists of type parameters.
class RelatedTypeParameters {
@@ -237,36 +238,53 @@
}
@override
- DartType flatten(DartType type) {
- if (identical(type, UnknownInferredType.instance)) {
- return type;
+ DartType flatten(DartType T) {
+ if (identical(T, UnknownInferredType.instance)) {
+ return T;
}
// if T is S? then flatten(T) = flatten(S)?
// if T is S* then flatten(T) = flatten(S)*
- NullabilitySuffix nullabilitySuffix = type.nullabilitySuffix;
+ final nullabilitySuffix = T.nullabilitySuffix;
if (nullabilitySuffix != NullabilitySuffix.none) {
- var S = (type as TypeImpl).withNullability(NullabilitySuffix.none);
+ final S = (T as TypeImpl).withNullability(NullabilitySuffix.none);
return (flatten(S) as TypeImpl).withNullability(nullabilitySuffix);
}
- // otherwise if T is FutureOr<S> then flatten(T) = S
- // otherwise if T is Future<S> then flatten(T) = S (shortcut)
- if (type is InterfaceType) {
- if (type.isDartAsyncFutureOr || type.isDartAsyncFuture) {
- return type.typeArguments[0];
+ // If T is X & S for some type variable X and type S then:
+ if (T is TypeParameterTypeImpl) {
+ final S = T.promotedBound;
+ if (S != null) {
+ // * if S has future type U then flatten(T) = flatten(U)
+ final futureType = this.futureType(S);
+ if (futureType != null) {
+ return flatten(futureType);
+ }
+ // * otherwise, flatten(T) = flatten(X)
+ return flatten(
+ TypeParameterTypeImpl(
+ element: T.element,
+ nullabilitySuffix: nullabilitySuffix,
+ ),
+ );
}
}
- // otherwise if T <: Future then let S be a type such that T <: Future<S>
- // and for all R, if T <: Future<R> then S <: R; then flatten(T) = S
- var futureType = type.asInstanceOf(typeProvider.futureElement);
- if (futureType != null) {
- return futureType.typeArguments[0];
+ // If T has future type Future<S> or FutureOr<S> then flatten(T) = S
+ // If T has future type Future<S>? or FutureOr<S>? then flatten(T) = S?
+ final futureType = this.futureType(T);
+ if (futureType is InterfaceType) {
+ if (futureType.isDartAsyncFuture || futureType.isDartAsyncFutureOr) {
+ final S = futureType.typeArguments[0] as TypeImpl;
+ if (futureType.nullabilitySuffix == NullabilitySuffix.question) {
+ return S.withNullability(NullabilitySuffix.question);
+ }
+ return S;
+ }
}
// otherwise flatten(T) = T
- return type;
+ return T;
}
DartType futureOrBase(DartType type) {
@@ -282,6 +300,23 @@
return type;
}
+ /// We say that S is the future type of a type T in the following cases,
+ /// using the first applicable case:
+ @visibleForTesting
+ DartType? futureType(DartType T) {
+ // T implements S, and there is a U such that S is Future<U>
+ if (T.nullabilitySuffix != NullabilitySuffix.question) {
+ final result = T.asInstanceOf(typeProvider.futureElement);
+ if (result != null) {
+ return result;
+ }
+ }
+
+ // T is S bounded, and there is a U such that S is FutureOr<U>,
+ // Future<U>?, or FutureOr<U>?.
+ return _futureTypeOfBounded(T);
+ }
+
/// Compute "future value type" of [T].
///
/// https://github.com/dart-lang/language/
@@ -759,10 +794,10 @@
// If the subtype relation goes the other way, allow the implicit downcast.
if (isSubtypeOf(toType, fromType)) {
- // TODO(leafp,jmesserly): we emit warnings/hints for these in
- // src/task/strong/checker.dart, which is a bit inconsistent. That
- // code should be handled into places that use isAssignableTo, such as
- // ErrorVerifier.
+ // TODO(leafp,jmesserly): we emit warnings for these in
+ // `src/task/strong/checker.dart`, which is a bit inconsistent. That code
+ // should be handled into places that use `isAssignableTo`, such as
+ // [ErrorVerifier].
return true;
}
@@ -1065,7 +1100,7 @@
@override
bool isNonNullable(DartType type) {
- if (type.isDynamic || type.isVoid || type.isDartCoreNull) {
+ if (type.isDynamic || type is VoidType || type.isDartCoreNull) {
return false;
} else if (type is TypeParameterTypeImpl && type.promotedBound != null) {
return isNonNullable(type.promotedBound!);
@@ -1106,7 +1141,7 @@
@override
bool isNullable(DartType type) {
- if (type.isDynamic || type.isVoid || type.isDartCoreNull) {
+ if (type.isDynamic || type is VoidType || type.isDartCoreNull) {
return true;
} else if (type is TypeParameterTypeImpl && type.promotedBound != null) {
return isNullable(type.promotedBound!);
@@ -1148,7 +1183,7 @@
@override
bool isStrictlyNonNullable(DartType type) {
- if (type.isDynamic || type.isVoid || type.isDartCoreNull) {
+ if (type.isDynamic || type is VoidType || type.isDartCoreNull) {
return false;
} else if (type.nullabilitySuffix != NullabilitySuffix.none) {
return false;
@@ -1688,6 +1723,50 @@
}).toFixedList();
}
+ /// `S` is the future type of a type `T` in the following cases, using the
+ /// first applicable case:
+ /// * see [futureType].
+ /// * `T` is `S` bounded, and there is a `U` such that `S` is `FutureOr<U>`,
+ /// `Future<U>?`, or `FutureOr<U>?`.
+ ///
+ /// 17.15.3: For a given type `T0`, we introduce the notion of a `T0` bounded
+ /// type: `T0` itself is `T0` bounded; if `B` is `T0` bounded and `X` is a
+ /// type variable with bound `B` then `X` is `T0` bounded; finally, if `B`
+ /// is `T0` bounded and `X` is a type variable then `X&B` is `T0` bounded.
+ DartType? _futureTypeOfBounded(DartType T) {
+ if (T is InterfaceType) {
+ if (T.nullabilitySuffix != NullabilitySuffix.question) {
+ if (T.isDartAsyncFutureOr) {
+ return T;
+ }
+ } else {
+ if (T.isDartAsyncFutureOr || T.isDartAsyncFuture) {
+ return T;
+ }
+ }
+ }
+
+ if (T is TypeParameterTypeImpl) {
+ final bound = T.element.bound;
+ if (bound != null) {
+ final result = _futureTypeOfBounded(bound);
+ if (result != null) {
+ return result;
+ }
+ }
+
+ final promotedBound = T.promotedBound;
+ if (promotedBound != null) {
+ final result = _futureTypeOfBounded(promotedBound);
+ if (result != null) {
+ return result;
+ }
+ }
+ }
+
+ return null;
+ }
+
DartType _refineBinaryExpressionTypeLegacy(DartType leftType,
TokenType operator, DartType rightType, DartType currentType) {
if (leftType is TypeParameterType && leftType.bound.isDartCoreNum) {
diff --git a/analyzer/lib/src/dart/error/hint_codes.g.dart b/analyzer/lib/src/dart/error/hint_codes.g.dart
index 9436803..d9438af 100644
--- a/analyzer/lib/src/dart/error/hint_codes.g.dart
+++ b/analyzer/lib/src/dart/error/hint_codes.g.dart
@@ -15,73 +15,23 @@
import "package:analyzer/src/error/analyzer_error_code.dart";
class HintCode extends AnalyzerErrorCode {
- /// Users should not assign values marked `@doNotStore`.
- ///
- /// Parameters:
- /// 0: the name of the field or variable
- static const HintCode ASSIGNMENT_OF_DO_NOT_STORE = HintCode(
- 'ASSIGNMENT_OF_DO_NOT_STORE',
- "'{0}' is marked 'doNotStore' and shouldn't be assigned to a field or "
- "top-level variable.",
- correctionMessage: "Try removing the assignment.",
- hasPublishedDocs: true,
- );
-
/// When the target expression uses '?.' operator, it can be `null`, so all the
/// subsequent invocations should also use '?.' operator.
///
/// Note: This diagnostic is only generated in pre-null safe code.
+ ///
+ /// Note: Since this diagnostic is only produced in pre-null safe code, we do
+ /// not plan to go through the exercise of converting it to a Warning.
static const HintCode CAN_BE_NULL_AFTER_NULL_AWARE = HintCode(
'CAN_BE_NULL_AFTER_NULL_AWARE',
"The receiver uses '?.', so its value can be null.",
correctionMessage: "Replace the '.' with a '?.' in the invocation.",
);
- /// Dead code is code that is never reached, this can happen for instance if a
- /// statement follows a return statement.
- ///
/// No parameters.
- static const HintCode DEAD_CODE = HintCode(
- 'DEAD_CODE',
- "Dead code.",
- correctionMessage:
- "Try removing the code, or fixing the code before it so that it can be "
- "reached.",
- hasPublishedDocs: true,
- );
-
- /// Dead code is code that is never reached. This case covers cases where the
- /// user has catch clauses after `catch (e)` or `on Object catch (e)`.
///
- /// No parameters.
- static const HintCode DEAD_CODE_CATCH_FOLLOWING_CATCH = HintCode(
- 'DEAD_CODE_CATCH_FOLLOWING_CATCH',
- "Dead code: Catch clauses after a 'catch (e)' or an 'on Object catch (e)' "
- "are never reached.",
- correctionMessage:
- "Try reordering the catch clauses so that they can be reached, or "
- "removing the unreachable catch clauses.",
- hasPublishedDocs: true,
- );
-
- /// Dead code is code that is never reached. This case covers cases where the
- /// user has an on-catch clause such as `on A catch (e)`, where a supertype of
- /// `A` was already caught.
- ///
- /// Parameters:
- /// 0: name of the subtype
- /// 1: name of the supertype
- static const HintCode DEAD_CODE_ON_CATCH_SUBTYPE = HintCode(
- 'DEAD_CODE_ON_CATCH_SUBTYPE',
- "Dead code: This on-catch block won't be executed because '{0}' is a "
- "subtype of '{1}' and hence will have been caught already.",
- correctionMessage:
- "Try reordering the catch clauses so that this block can be reached, "
- "or removing the unreachable catch clause.",
- hasPublishedDocs: true,
- );
-
- /// No parameters.
+ /// Note: Since this diagnostic is only produced in pre-3.0 code, we do not
+ /// plan to go through the exercise of converting it to a Warning.
static const HintCode DEPRECATED_COLON_FOR_DEFAULT_VALUE = HintCode(
'DEPRECATED_COLON_FOR_DEFAULT_VALUE',
"Using a colon as a separator before a default value is deprecated and "
@@ -153,34 +103,6 @@
hasPublishedDocs: true,
);
- /// It is a bad practice for a source file in a package "lib" directory
- /// hierarchy to traverse outside that directory hierarchy. For example, a
- /// source file in the "lib" directory should not contain a directive such as
- /// `import '../web/some.dart'` which references a file outside the lib
- /// directory.
- static const HintCode FILE_IMPORT_INSIDE_LIB_REFERENCES_FILE_OUTSIDE =
- HintCode(
- 'FILE_IMPORT_INSIDE_LIB_REFERENCES_FILE_OUTSIDE',
- "A file in the 'lib' directory shouldn't import a file outside the 'lib' "
- "directory.",
- correctionMessage:
- "Try removing the import, or moving the imported file inside the 'lib' "
- "directory.",
- );
-
- /// It is a bad practice for a source file outside a package "lib" directory
- /// hierarchy to traverse into that directory hierarchy. For example, a source
- /// file in the "web" directory should not contain a directive such as
- /// `import '../lib/some.dart'` which references a file inside the lib
- /// directory.
- static const HintCode FILE_IMPORT_OUTSIDE_LIB_REFERENCES_FILE_INSIDE =
- HintCode(
- 'FILE_IMPORT_OUTSIDE_LIB_REFERENCES_FILE_INSIDE',
- "A file outside the 'lib' directory shouldn't reference a file inside the "
- "'lib' directory using a relative path.",
- correctionMessage: "Try using a 'package:' URI instead.",
- );
-
/// No parameters.
static const HintCode IMPORT_DEFERRED_LIBRARY_WITH_LOAD_FUNCTION = HintCode(
'IMPORT_DEFERRED_LIBRARY_WITH_LOAD_FUNCTION',
@@ -204,144 +126,6 @@
hasPublishedDocs: true,
);
- /// This hint is generated anywhere where a `@sealed` class is used as a
- /// a superclass constraint of a mixin.
- ///
- /// Parameters:
- /// 0: the name of the sealed class
- static const HintCode MIXIN_ON_SEALED_CLASS = HintCode(
- 'MIXIN_ON_SEALED_CLASS',
- "The class '{0}' shouldn't be used as a mixin constraint because it is "
- "sealed, and any class mixing in this mixin must have '{0}' as a "
- "superclass.",
- correctionMessage:
- "Try composing with this class, or refer to its documentation for more "
- "information.",
- hasPublishedDocs: true,
- );
-
- /// Generate a hint for non-const instance creation using a constructor
- /// annotated with `@literal`.
- ///
- /// Parameters:
- /// 0: the name of the class defining the annotated constructor
- static const HintCode NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR = HintCode(
- 'NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR',
- "This instance creation must be 'const', because the {0} constructor is "
- "marked as '@literal'.",
- correctionMessage: "Try adding a 'const' keyword.",
- hasPublishedDocs: true,
- );
-
- /// Generate a hint for non-const instance creation (with the `new` keyword)
- /// using a constructor annotated with `@literal`.
- ///
- /// Parameters:
- /// 0: the name of the class defining the annotated constructor
- static const HintCode NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR_USING_NEW =
- HintCode(
- 'NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR',
- "This instance creation must be 'const', because the {0} constructor is "
- "marked as '@literal'.",
- correctionMessage: "Try replacing the 'new' keyword with 'const'.",
- hasPublishedDocs: true,
- uniqueName: 'NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR_USING_NEW',
- );
-
- /// No parameters.
- static const HintCode NULL_CHECK_ALWAYS_FAILS = HintCode(
- 'NULL_CHECK_ALWAYS_FAILS',
- "This null-check will always throw an exception because the expression "
- "will always evaluate to 'null'.",
- hasPublishedDocs: true,
- );
-
- /// A field with the override annotation does not override a getter or setter.
- ///
- /// No parameters.
- static const HintCode OVERRIDE_ON_NON_OVERRIDING_FIELD = HintCode(
- 'OVERRIDE_ON_NON_OVERRIDING_MEMBER',
- "The field doesn't override an inherited getter or setter.",
- correctionMessage:
- "Try updating this class to match the superclass, or removing the "
- "override annotation.",
- hasPublishedDocs: true,
- uniqueName: 'OVERRIDE_ON_NON_OVERRIDING_FIELD',
- );
-
- /// A getter with the override annotation does not override an existing getter.
- ///
- /// No parameters.
- static const HintCode OVERRIDE_ON_NON_OVERRIDING_GETTER = HintCode(
- 'OVERRIDE_ON_NON_OVERRIDING_MEMBER',
- "The getter doesn't override an inherited getter.",
- correctionMessage:
- "Try updating this class to match the superclass, or removing the "
- "override annotation.",
- hasPublishedDocs: true,
- uniqueName: 'OVERRIDE_ON_NON_OVERRIDING_GETTER',
- );
-
- /// A method with the override annotation does not override an existing method.
- ///
- /// No parameters.
- static const HintCode OVERRIDE_ON_NON_OVERRIDING_METHOD = HintCode(
- 'OVERRIDE_ON_NON_OVERRIDING_MEMBER',
- "The method doesn't override an inherited method.",
- correctionMessage:
- "Try updating this class to match the superclass, or removing the "
- "override annotation.",
- hasPublishedDocs: true,
- uniqueName: 'OVERRIDE_ON_NON_OVERRIDING_METHOD',
- );
-
- /// A setter with the override annotation does not override an existing setter.
- ///
- /// No parameters.
- static const HintCode OVERRIDE_ON_NON_OVERRIDING_SETTER = HintCode(
- 'OVERRIDE_ON_NON_OVERRIDING_MEMBER',
- "The setter doesn't override an inherited setter.",
- correctionMessage:
- "Try updating this class to match the superclass, or removing the "
- "override annotation.",
- hasPublishedDocs: true,
- uniqueName: 'OVERRIDE_ON_NON_OVERRIDING_SETTER',
- );
-
- /// When "strict-raw-types" is enabled, "raw types" must have type arguments.
- ///
- /// A "raw type" is a type name that does not use inference to fill in missing
- /// type arguments; instead, each type argument is instantiated to its bound.
- ///
- /// Parameters:
- /// 0: the name of the generic type
- static const HintCode STRICT_RAW_TYPE = HintCode(
- 'STRICT_RAW_TYPE',
- "The generic type '{0}' should have explicit type arguments but doesn't.",
- correctionMessage: "Use explicit type arguments for '{0}'.",
- );
-
- /// Parameters:
- /// 0: the name of the sealed class
- static const HintCode SUBTYPE_OF_SEALED_CLASS = HintCode(
- 'SUBTYPE_OF_SEALED_CLASS',
- "The class '{0}' shouldn't be extended, mixed in, or implemented because "
- "it's sealed.",
- correctionMessage:
- "Try composing instead of inheriting, or refer to the documentation of "
- "'{0}' for more information.",
- hasPublishedDocs: true,
- );
-
- /// Parameters:
- /// 0: the name of the undefined parameter
- /// 1: the name of the targeted member
- static const HintCode UNDEFINED_REFERENCED_PARAMETER = HintCode(
- 'UNDEFINED_REFERENCED_PARAMETER',
- "The parameter '{0}' isn't defined by '{1}'.",
- hasPublishedDocs: true,
- );
-
/// Parameters:
/// 0: the name of the non-diagnostic being ignored
static const HintCode UNIGNORABLE_IGNORE = HintCode(
@@ -392,88 +176,12 @@
);
/// No parameters.
- static const HintCode UNNECESSARY_NAN_COMPARISON_FALSE = HintCode(
- 'UNNECESSARY_NAN_COMPARISON',
- "A double can't equal 'double.nan', so the condition is always 'false'.",
- correctionMessage: "Try using 'double.isNan', or removing the condition.",
- uniqueName: 'UNNECESSARY_NAN_COMPARISON_FALSE',
- );
-
- /// No parameters.
- static const HintCode UNNECESSARY_NAN_COMPARISON_TRUE = HintCode(
- 'UNNECESSARY_NAN_COMPARISON',
- "A double can't equal 'double.nan', so the condition is always 'true'.",
- correctionMessage: "Try using 'double.isNan', or removing the condition.",
- uniqueName: 'UNNECESSARY_NAN_COMPARISON_TRUE',
- );
-
- /// No parameters.
- static const HintCode UNNECESSARY_NO_SUCH_METHOD = HintCode(
- 'UNNECESSARY_NO_SUCH_METHOD',
- "Unnecessary 'noSuchMethod' declaration.",
- correctionMessage: "Try removing the declaration of 'noSuchMethod'.",
- hasPublishedDocs: true,
- );
-
- /// No parameters.
- static const HintCode UNNECESSARY_NULL_COMPARISON_FALSE = HintCode(
- 'UNNECESSARY_NULL_COMPARISON',
- "The operand can't be null, so the condition is always 'false'.",
- correctionMessage:
- "Try removing the condition, an enclosing condition, or the whole "
- "conditional statement.",
- hasPublishedDocs: true,
- uniqueName: 'UNNECESSARY_NULL_COMPARISON_FALSE',
- );
-
- /// No parameters.
- static const HintCode UNNECESSARY_NULL_COMPARISON_TRUE = HintCode(
- 'UNNECESSARY_NULL_COMPARISON',
- "The operand can't be null, so the condition is always 'true'.",
- correctionMessage: "Remove the condition.",
- hasPublishedDocs: true,
- uniqueName: 'UNNECESSARY_NULL_COMPARISON_TRUE',
- );
-
- /// Parameters:
- /// 0: the name of the type
- static const HintCode UNNECESSARY_QUESTION_MARK = HintCode(
- 'UNNECESSARY_QUESTION_MARK',
- "The '?' is unnecessary because '{0}' is nullable without it.",
- hasPublishedDocs: true,
- );
-
- /// No parameters.
- static const HintCode UNNECESSARY_SET_LITERAL = HintCode(
- 'UNNECESSARY_SET_LITERAL',
- "Braces unnecessarily wrap this expression in a set literal.",
- correctionMessage: "Try removing the set literal around the expression.",
- );
-
- /// No parameters.
- static const HintCode UNNECESSARY_TYPE_CHECK_FALSE = HintCode(
- 'UNNECESSARY_TYPE_CHECK',
- "Unnecessary type check; the result is always 'false'.",
- correctionMessage:
- "Try correcting the type check, or removing the type check.",
- hasPublishedDocs: true,
- uniqueName: 'UNNECESSARY_TYPE_CHECK_FALSE',
- );
-
- /// No parameters.
- static const HintCode UNNECESSARY_TYPE_CHECK_TRUE = HintCode(
- 'UNNECESSARY_TYPE_CHECK',
- "Unnecessary type check; the result is always 'true'.",
- correctionMessage:
- "Try correcting the type check, or removing the type check.",
- hasPublishedDocs: true,
- uniqueName: 'UNNECESSARY_TYPE_CHECK_TRUE',
- );
-
- /// No parameters.
static const HintCode UNREACHABLE_SWITCH_CASE = HintCode(
'UNREACHABLE_SWITCH_CASE',
"This case is covered by the previous cases.",
+ correctionMessage:
+ "Try removing the case clause, or restructuring the preceding "
+ "patterns.",
);
/// Parameters:
@@ -514,17 +222,6 @@
);
/// Parameters:
- /// 0: the label that isn't used
- static const HintCode UNUSED_LABEL = HintCode(
- 'UNUSED_LABEL',
- "The label '{0}' isn't used.",
- correctionMessage:
- "Try removing the label, or using it in either a 'break' or 'continue' "
- "statement.",
- hasPublishedDocs: true,
- );
-
- /// Parameters:
/// 0: the name of the unused variable
static const HintCode UNUSED_LOCAL_VARIABLE = HintCode(
'UNUSED_LOCAL_VARIABLE',
@@ -534,34 +231,6 @@
);
/// Parameters:
- /// 0: the name of the annotated method, property or function
- static const HintCode UNUSED_RESULT = HintCode(
- 'UNUSED_RESULT',
- "The value of '{0}' should be used.",
- correctionMessage:
- "Try using the result by invoking a member, passing it to a function, "
- "or returning it from this function.",
- hasPublishedDocs: true,
- );
-
- /// The result of invoking a method, property, or function annotated with
- /// `@useResult` must be used (assigned, passed to a function as an argument,
- /// or returned by a function).
- ///
- /// Parameters:
- /// 0: the name of the annotated method, property or function
- /// 1: message details
- static const HintCode UNUSED_RESULT_WITH_MESSAGE = HintCode(
- 'UNUSED_RESULT',
- "'{0}' should be used. {1}.",
- correctionMessage:
- "Try using the result by invoking a member, passing it to a function, "
- "or returning it from this function.",
- hasPublishedDocs: true,
- uniqueName: 'UNUSED_RESULT_WITH_MESSAGE',
- );
-
- /// Parameters:
/// 0: the name that is shown but not used
static const HintCode UNUSED_SHOWN_NAME = HintCode(
'UNUSED_SHOWN_NAME',
diff --git a/analyzer/lib/src/dart/error/syntactic_errors.g.dart b/analyzer/lib/src/dart/error/syntactic_errors.g.dart
index 5528d70..9f8e58f 100644
--- a/analyzer/lib/src/dart/error/syntactic_errors.g.dart
+++ b/analyzer/lib/src/dart/error/syntactic_errors.g.dart
@@ -156,6 +156,9 @@
ParserErrorCode.INVALID_CONSTANT_PATTERN_GENERIC,
ParserErrorCode.INVALID_CONSTANT_CONST_PREFIX,
ParserErrorCode.INVALID_CONSTANT_PATTERN_BINARY,
+ ParserErrorCode.FINAL_MIXIN_CLASS,
+ ParserErrorCode.INTERFACE_MIXIN_CLASS,
+ ParserErrorCode.SEALED_MIXIN_CLASS,
];
class ParserErrorCode extends ErrorCode {
@@ -462,20 +465,20 @@
static const ParserErrorCode EMPTY_RECORD_LITERAL_WITH_COMMA =
ParserErrorCode(
'EMPTY_RECORD_LITERAL_WITH_COMMA',
- "Record literal without fields can't have a trailing comma.",
+ "A record literal without fields can't have a trailing comma.",
correctionMessage: "Try removing the trailing comma.",
);
static const ParserErrorCode EMPTY_RECORD_TYPE_NAMED_FIELDS_LIST =
ParserErrorCode(
'EMPTY_RECORD_TYPE_NAMED_FIELDS_LIST',
- "Record type named fields list can't be empty.",
- correctionMessage: "Try adding a record type named field to the list.",
+ "The list of named fields in a record type can't be empty.",
+ correctionMessage: "Try adding a named field to the list.",
);
static const ParserErrorCode EMPTY_RECORD_TYPE_WITH_COMMA = ParserErrorCode(
'EMPTY_RECORD_TYPE_WITH_COMMA',
- "Record type without fields can't have a trailing comma.",
+ "A record type without fields can't have a trailing comma.",
correctionMessage: "Try removing the trailing comma.",
);
@@ -820,6 +823,12 @@
correctionMessage: "Try removing the keyword 'final'.",
);
+ static const ParserErrorCode FINAL_MIXIN_CLASS = ParserErrorCode(
+ 'FINAL_MIXIN_CLASS',
+ "A mixin class can't be declared 'final'.",
+ correctionMessage: "Try removing the 'final' keyword.",
+ );
+
static const ParserErrorCode FINAL_TYPEDEF = ParserErrorCode(
'FINAL_TYPEDEF',
"Typedefs can't be declared to be 'final'.",
@@ -897,6 +906,12 @@
"Try removing the initializer, or using a different kind of loop.",
);
+ static const ParserErrorCode INTERFACE_MIXIN_CLASS = ParserErrorCode(
+ 'INTERFACE_MIXIN_CLASS',
+ "A mixin class can't be declared 'interface'.",
+ correctionMessage: "Try removing the 'interface' keyword.",
+ );
+
static const ParserErrorCode INVALID_AWAIT_IN_FOR = ParserErrorCode(
'INVALID_AWAIT_IN_FOR',
"The keyword 'await' isn't allowed for a normal 'for' statement.",
@@ -1517,14 +1532,16 @@
static const ParserErrorCode RECORD_LITERAL_ONE_POSITIONAL_NO_TRAILING_COMMA =
ParserErrorCode(
'RECORD_LITERAL_ONE_POSITIONAL_NO_TRAILING_COMMA',
- "Record literal with one field requires a trailing comma.",
+ "A record literal with exactly one positional field requires a trailing "
+ "comma.",
correctionMessage: "Try adding a trailing comma.",
);
static const ParserErrorCode RECORD_TYPE_ONE_POSITIONAL_NO_TRAILING_COMMA =
ParserErrorCode(
'RECORD_TYPE_ONE_POSITIONAL_NO_TRAILING_COMMA',
- "Record type with one entry requires a trailing comma.",
+ "A record type with exactly one positional field requires a trailing "
+ "comma.",
correctionMessage: "Try adding a trailing comma.",
);
@@ -1544,6 +1561,12 @@
"Try making this a factory constructor, or remove the redirection.",
);
+ static const ParserErrorCode SEALED_MIXIN_CLASS = ParserErrorCode(
+ 'SEALED_MIXIN_CLASS',
+ "A mixin class can't be declared 'sealed'.",
+ correctionMessage: "Try removing the 'sealed' keyword.",
+ );
+
static const ParserErrorCode SETTER_CONSTRUCTOR = ParserErrorCode(
'SETTER_CONSTRUCTOR',
"Constructors can't be a setter.",
diff --git a/analyzer/lib/src/dart/resolver/assignment_expression_resolver.dart b/analyzer/lib/src/dart/resolver/assignment_expression_resolver.dart
index fd10c92..6284ee2 100644
--- a/analyzer/lib/src/dart/resolver/assignment_expression_resolver.dart
+++ b/analyzer/lib/src/dart/resolver/assignment_expression_resolver.dart
@@ -119,7 +119,7 @@
DartType rightType, {
required Map<DartType, NonPromotionReason> Function()? whyNotPromoted,
}) {
- if (!writeType.isVoid && _checkForUseOfVoidResult(right)) {
+ if (writeType is! VoidType && _checkForUseOfVoidResult(right)) {
return;
}
@@ -127,6 +127,20 @@
return;
}
+ if (writeType is RecordType) {
+ if (rightType is! RecordType && writeType.positionalFields.length == 1) {
+ var field = writeType.positionalFields.first;
+ if (_typeSystem.isAssignableTo(field.type, rightType)) {
+ _errorReporter.reportErrorForNode(
+ WarningCode.RECORD_LITERAL_ONE_POSITIONAL_NO_TRAILING_COMMA,
+ right,
+ [],
+ );
+ return;
+ }
+ }
+ }
+
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.INVALID_ASSIGNMENT,
right,
@@ -193,7 +207,7 @@
// Values of the type void cannot be used.
// Example: `y += 0`, is not allowed.
if (operatorType != TokenType.EQ) {
- if (leftType.isVoid) {
+ if (leftType is VoidType) {
_errorReporter.reportErrorForToken(
CompileTimeErrorCode.USE_OF_VOID_RESULT,
operator,
diff --git a/analyzer/lib/src/dart/resolver/ast_rewrite.dart b/analyzer/lib/src/dart/resolver/ast_rewrite.dart
index 89659f5..98e03a6 100644
--- a/analyzer/lib/src/dart/resolver/ast_rewrite.dart
+++ b/analyzer/lib/src/dart/resolver/ast_rewrite.dart
@@ -251,15 +251,21 @@
return node;
}
var prefix = node.prefix;
- var element = nameScope.lookup(prefix.name).getter;
- if (element is InterfaceElement) {
+ var prefixElement = nameScope.lookup(prefix.name).getter;
+ if (parent is ConstantPattern && prefixElement is PrefixElement) {
+ final element = prefixElement.scope.lookup(node.identifier.name).getter;
+ if (element is TypeDefiningElement) {
+ return _toPatternTypeLiteral(parent, node);
+ }
+ }
+ if (prefixElement is InterfaceElement) {
// Example:
// class C { C.named(); }
// C.named
return _toConstructorReference_prefixed(
- node: node, classElement: element);
- } else if (element is TypeAliasElement) {
- var aliasedType = element.aliasedType;
+ node: node, classElement: prefixElement);
+ } else if (prefixElement is TypeAliasElement) {
+ var aliasedType = prefixElement.aliasedType;
if (aliasedType is InterfaceType) {
// Example:
// class C { C.named(); }
@@ -376,6 +382,18 @@
return node;
}
+ AstNode simpleIdentifier(Scope nameScope, SimpleIdentifierImpl node) {
+ final parent = node.parent;
+ if (parent is ConstantPattern) {
+ final element = nameScope.lookup(node.name).getter;
+ if (element is TypeDefiningElement) {
+ return _toPatternTypeLiteral(parent, node);
+ }
+ }
+
+ return node;
+ }
+
AstNode _instanceCreation_prefix_type_name({
required MethodInvocationImpl node,
required PrefixedIdentifierImpl typeNameIdentifier,
@@ -627,4 +645,20 @@
NodeReplacer.replace(node, methodInvocation);
return methodInvocation;
}
+
+ TypeLiteralImpl _toPatternTypeLiteral(
+ ConstantPattern parent,
+ IdentifierImpl node,
+ ) {
+ final result = TypeLiteralImpl(
+ typeName: NamedTypeImpl(
+ name: node,
+ typeArguments: null,
+ question: null,
+ ),
+ );
+ result.staticType = _typeProvider.typeType;
+ NodeReplacer.replace(node, result, parent: parent);
+ return result;
+ }
}
diff --git a/analyzer/lib/src/dart/resolver/binary_expression_resolver.dart b/analyzer/lib/src/dart/resolver/binary_expression_resolver.dart
index 050a614..a4935f4 100644
--- a/analyzer/lib/src/dart/resolver/binary_expression_resolver.dart
+++ b/analyzer/lib/src/dart/resolver/binary_expression_resolver.dart
@@ -139,8 +139,8 @@
void reportNullComparison(SyntacticEntity start, SyntacticEntity end) {
var errorCode = notEqual
- ? HintCode.UNNECESSARY_NULL_COMPARISON_FALSE
- : HintCode.UNNECESSARY_NULL_COMPARISON_TRUE;
+ ? WarningCode.UNNECESSARY_NULL_COMPARISON_FALSE
+ : WarningCode.UNNECESSARY_NULL_COMPARISON_TRUE;
var offset = start.offset;
_errorReporter.reportErrorForOffset(errorCode, offset, end.end - offset);
}
diff --git a/analyzer/lib/src/dart/resolver/body_inference_context.dart b/analyzer/lib/src/dart/resolver/body_inference_context.dart
index 54d18dc..efa7636 100644
--- a/analyzer/lib/src/dart/resolver/body_inference_context.dart
+++ b/analyzer/lib/src/dart/resolver/body_inference_context.dart
@@ -127,11 +127,11 @@
// If `R` is `void`, or the function literal is marked `async` and `R` is
// `FutureOr<void>`, let `S` be `void`.
if (_typeSystem.isNonNullableByDefault) {
- if (R.isVoid ||
+ if (R is VoidType ||
isAsynchronous &&
R is InterfaceType &&
R.isDartAsyncFutureOr &&
- R.typeArguments[0].isVoid) {
+ R.typeArguments[0] is VoidType) {
return VoidTypeImpl.instance;
}
}
diff --git a/analyzer/lib/src/dart/resolver/exit_detector.dart b/analyzer/lib/src/dart/resolver/exit_detector.dart
index 2d6e4cb..1aca5b8 100644
--- a/analyzer/lib/src/dart/resolver/exit_detector.dart
+++ b/analyzer/lib/src/dart/resolver/exit_detector.dart
@@ -504,6 +504,11 @@
_visitStatements(node.statements);
@override
+ bool visitSwitchPatternCase(SwitchPatternCase node) {
+ return _visitStatements(node.statements);
+ }
+
+ @override
bool visitSwitchStatement(SwitchStatement node) {
bool outerBreakValue = _enclosingBlockContainsBreak;
_enclosingBlockContainsBreak = false;
diff --git a/analyzer/lib/src/dart/resolver/extension_member_resolver.dart b/analyzer/lib/src/dart/resolver/extension_member_resolver.dart
index 0e5d140..6dd7018 100644
--- a/analyzer/lib/src/dart/resolver/extension_member_resolver.dart
+++ b/analyzer/lib/src/dart/resolver/extension_member_resolver.dart
@@ -222,7 +222,7 @@
substitution,
);
- if (receiverType.isVoid) {
+ if (receiverType is VoidType) {
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.USE_OF_VOID_RESULT, receiverExpression);
} else if (!_typeSystem.isAssignableTo(receiverType, extendedType)) {
diff --git a/analyzer/lib/src/dart/resolver/function_expression_invocation_resolver.dart b/analyzer/lib/src/dart/resolver/function_expression_invocation_resolver.dart
index 331cb62..5168a31 100644
--- a/analyzer/lib/src/dart/resolver/function_expression_invocation_resolver.dart
+++ b/analyzer/lib/src/dart/resolver/function_expression_invocation_resolver.dart
@@ -48,29 +48,17 @@
}
var receiverType = function.typeOrThrow;
- if (receiverType is InterfaceType) {
- // Note: in this circumstance it's not necessary to call
- // `_nullableDereferenceVerifier.expression` because
- // `_resolveReceiverInterfaceType` calls `TypePropertyResolver.resolve`,
- // which does the necessary null checking.
- _resolveReceiverInterfaceType(
- node, function, receiverType, whyNotPromotedList,
- contextType: contextType);
- return;
- }
-
if (_checkForUseOfVoidResult(function, receiverType)) {
_unresolved(node, DynamicTypeImpl.instance, whyNotPromotedList,
contextType: contextType);
return;
}
- _nullableDereferenceVerifier.expression(
- CompileTimeErrorCode.UNCHECKED_INVOCATION_OF_NULLABLE_VALUE,
- function,
- );
-
if (receiverType is FunctionType) {
+ _nullableDereferenceVerifier.expression(
+ CompileTimeErrorCode.UNCHECKED_INVOCATION_OF_NULLABLE_VALUE,
+ function,
+ );
_resolve(node, receiverType, whyNotPromotedList,
contextType: contextType);
return;
@@ -84,8 +72,40 @@
return;
}
- _unresolved(node, DynamicTypeImpl.instance, whyNotPromotedList,
- contextType: contextType);
+ var result = _typePropertyResolver.resolve(
+ receiver: function,
+ receiverType: receiverType,
+ name: FunctionElement.CALL_METHOD_NAME,
+ propertyErrorEntity: function,
+ nameErrorEntity: function,
+ );
+ var callElement = result.getter;
+
+ if (callElement == null) {
+ if (result.needsGetterError) {
+ _errorReporter.reportErrorForNode(
+ CompileTimeErrorCode.INVOCATION_OF_NON_FUNCTION_EXPRESSION,
+ function,
+ );
+ }
+ _unresolved(node, DynamicTypeImpl.instance, whyNotPromotedList,
+ contextType: contextType);
+ return;
+ }
+
+ if (callElement.kind != ElementKind.METHOD) {
+ _errorReporter.reportErrorForNode(
+ CompileTimeErrorCode.INVOCATION_OF_NON_FUNCTION_EXPRESSION,
+ function,
+ );
+ _unresolved(node, DynamicTypeImpl.instance, whyNotPromotedList,
+ contextType: contextType);
+ return;
+ }
+
+ node.staticElement = callElement;
+ var rawType = callElement.type;
+ _resolve(node, rawType, whyNotPromotedList, contextType: contextType);
}
/// Check for situations where the result of a method or function is used,
@@ -158,48 +178,6 @@
_resolve(node, rawType, whyNotPromotedList, contextType: contextType);
}
- void _resolveReceiverInterfaceType(
- FunctionExpressionInvocationImpl node,
- Expression function,
- InterfaceType receiverType,
- List<WhyNotPromotedGetter> whyNotPromotedList,
- {required DartType? contextType}) {
- var result = _typePropertyResolver.resolve(
- receiver: function,
- receiverType: receiverType,
- name: FunctionElement.CALL_METHOD_NAME,
- propertyErrorEntity: function,
- nameErrorEntity: function,
- );
- var callElement = result.getter;
-
- if (callElement == null) {
- if (result.needsGetterError) {
- _errorReporter.reportErrorForNode(
- CompileTimeErrorCode.INVOCATION_OF_NON_FUNCTION_EXPRESSION,
- function,
- );
- }
- _unresolved(node, DynamicTypeImpl.instance, whyNotPromotedList,
- contextType: contextType);
- return;
- }
-
- if (callElement.kind != ElementKind.METHOD) {
- _errorReporter.reportErrorForNode(
- CompileTimeErrorCode.INVOCATION_OF_NON_FUNCTION_EXPRESSION,
- function,
- );
- _unresolved(node, DynamicTypeImpl.instance, whyNotPromotedList,
- contextType: contextType);
- return;
- }
-
- node.staticElement = callElement;
- var rawType = callElement.type;
- _resolve(node, rawType, whyNotPromotedList, contextType: contextType);
- }
-
void _unresolved(FunctionExpressionInvocationImpl node, DartType type,
List<WhyNotPromotedGetter> whyNotPromotedList,
{required DartType? contextType}) {
diff --git a/analyzer/lib/src/dart/resolver/legacy_type_asserter.dart b/analyzer/lib/src/dart/resolver/legacy_type_asserter.dart
index 580be8a..e29344f 100644
--- a/analyzer/lib/src/dart/resolver/legacy_type_asserter.dart
+++ b/analyzer/lib/src/dart/resolver/legacy_type_asserter.dart
@@ -127,7 +127,7 @@
return;
}
- if (type.isDynamic || type.isVoid) {
+ if (type.isDynamic || type is VoidType) {
return;
}
diff --git a/analyzer/lib/src/dart/resolver/list_pattern_resolver.dart b/analyzer/lib/src/dart/resolver/list_pattern_resolver.dart
index ec5a887..4edfbdf 100644
--- a/analyzer/lib/src/dart/resolver/list_pattern_resolver.dart
+++ b/analyzer/lib/src/dart/resolver/list_pattern_resolver.dart
@@ -30,8 +30,10 @@
}
}
- node.requiredType = resolverVisitor.analyzeListPattern(context, node,
- elementType: typeArguments?.arguments.first.typeOrThrow,
- elements: node.elements);
+ node.requiredType = resolverVisitor
+ .analyzeListPattern(context, node,
+ elementType: typeArguments?.arguments.first.typeOrThrow,
+ elements: node.elements)
+ .requiredType;
}
}
diff --git a/analyzer/lib/src/dart/resolver/property_element_resolver.dart b/analyzer/lib/src/dart/resolver/property_element_resolver.dart
index a4dc213..94ee843 100644
--- a/analyzer/lib/src/dart/resolver/property_element_resolver.dart
+++ b/analyzer/lib/src/dart/resolver/property_element_resolver.dart
@@ -77,7 +77,7 @@
var targetType = target.typeOrThrow;
targetType = _typeSystem.resolveToBound(targetType);
- if (targetType.isVoid) {
+ if (targetType is VoidType) {
// TODO(scheglov) Report directly in TypePropertyResolver?
_reportUnresolvedIndex(
node,
@@ -431,7 +431,7 @@
);
}
- if (targetType.isVoid) {
+ if (targetType is VoidType) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.USE_OF_VOID_RESULT,
propertyName,
diff --git a/analyzer/lib/src/dart/resolver/resolution_visitor.dart b/analyzer/lib/src/dart/resolver/resolution_visitor.dart
index 9a08bbb..d83363d 100644
--- a/analyzer/lib/src/dart/resolver/resolution_visitor.dart
+++ b/analyzer/lib/src/dart/resolver/resolution_visitor.dart
@@ -2,6 +2,8 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
+import 'package:_fe_analyzer_shared/src/type_inference/type_analyzer.dart'
+ as shared;
import 'package:_fe_analyzer_shared/src/type_inference/variable_bindings.dart';
import 'package:analyzer/dart/analysis/features.dart';
import 'package:analyzer/dart/ast/ast.dart';
@@ -1155,6 +1157,16 @@
}
@override
+ void visitSimpleIdentifier(covariant SimpleIdentifierImpl node) {
+ final newNode = _astRewriter.simpleIdentifier(_nameScope, node);
+ if (newNode != node) {
+ return newNode.accept(this);
+ }
+
+ super.visitSimpleIdentifier(node);
+ }
+
+ @override
void visitSuperFormalParameter(covariant SuperFormalParameterImpl node) {
SuperFormalParameterElementImpl element;
if (node.parent is DefaultFormalParameter) {
@@ -1699,7 +1711,7 @@
JoinPatternVariableElementImpl joinPatternVariables({
required Object key,
required List<PromotableElement> components,
- required bool isConsistent,
+ required shared.JoinedPatternVariableInconsistency inconsistency,
}) {
var first = components.first;
List<PatternVariableElementImpl> expandedVariables;
@@ -1723,10 +1735,11 @@
first.name,
-1,
expandedVariables,
- isConsistent &&
- components.every((element) =>
- element is! JoinPatternVariableElementImpl ||
- element.isConsistent),
+ inconsistency.maxWithAll(
+ components
+ .whereType<JoinPatternVariableElementImpl>()
+ .map((e) => e.inconsistency),
+ ),
)..enclosingElement = first.enclosingElement;
}
}
@@ -1758,6 +1771,7 @@
[name],
),
);
+ duplicate.isDuplicate = true;
}
@override
diff --git a/analyzer/lib/src/dart/resolver/shared_type_analyzer.dart b/analyzer/lib/src/dart/resolver/shared_type_analyzer.dart
index 347957e..ca99c2f 100644
--- a/analyzer/lib/src/dart/resolver/shared_type_analyzer.dart
+++ b/analyzer/lib/src/dart/resolver/shared_type_analyzer.dart
@@ -22,25 +22,12 @@
class SharedTypeAnalyzerErrors
implements
shared.TypeAnalyzerErrors<AstNode, Statement, Expression,
- PromotableElement, DartType, DartPattern> {
+ PromotableElement, DartType, DartPattern, void> {
final ErrorReporter _errorReporter;
SharedTypeAnalyzerErrors(this._errorReporter);
@override
- void argumentTypeNotAssignable({
- required Expression argument,
- required DartType argumentType,
- required DartType parameterType,
- }) {
- _errorReporter.reportErrorForNode(
- CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE,
- argument,
- [argumentType, parameterType],
- );
- }
-
- @override
void assertInErrorRecovery() {}
@override
@@ -175,15 +162,6 @@
}
@override
- void nonExhaustiveSwitch(
- {required AstNode node, required DartType scrutineeType}) {
- _errorReporter.reportErrorForNode(
- CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH,
- node,
- [scrutineeType, scrutineeType.toString()]);
- }
-
- @override
void patternDoesNotAllowLate({required AstNode pattern}) {
throw UnimplementedError('TODO(paulberry)');
}
@@ -225,6 +203,19 @@
}
@override
+ void relationalPatternOperandTypeNotAssignable({
+ required covariant RelationalPatternImpl pattern,
+ required DartType operandType,
+ required DartType parameterType,
+ }) {
+ _errorReporter.reportErrorForNode(
+ CompileTimeErrorCode.RELATIONAL_PATTERN_OPERAND_TYPE_NOT_ASSIGNABLE,
+ pattern.operand,
+ [operandType, parameterType, pattern.operator.lexeme],
+ );
+ }
+
+ @override
void relationalPatternOperatorReturnTypeNotAssignableToBool({
required covariant RelationalPatternImpl pattern,
required DartType returnType,
diff --git a/analyzer/lib/src/error/base_or_final_type_verifier.dart b/analyzer/lib/src/error/base_or_final_type_verifier.dart
new file mode 100644
index 0000000..ae6cd9e
--- /dev/null
+++ b/analyzer/lib/src/error/base_or_final_type_verifier.dart
@@ -0,0 +1,241 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/dart/analysis/features.dart';
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/dart/element/type.dart';
+import 'package:analyzer/diagnostic/diagnostic.dart';
+import 'package:analyzer/error/listener.dart';
+import 'package:analyzer/src/dart/element/element.dart';
+import 'package:analyzer/src/diagnostic/diagnostic.dart';
+import 'package:analyzer/src/error/codes.dart';
+
+/// Helper for verifying that subelements of a base or final element must be
+/// base, final, or sealed.
+class BaseOrFinalTypeVerifier {
+ final LibraryElement _definingLibrary;
+ final ErrorReporter _errorReporter;
+
+ BaseOrFinalTypeVerifier({
+ required LibraryElement definingLibrary,
+ required ErrorReporter errorReporter,
+ }) : _definingLibrary = definingLibrary,
+ _errorReporter = errorReporter;
+
+ /// Check to ensure the subelement of a base or final element must be base,
+ /// final, or sealed and that base elements are not implemented outside of its
+ /// library. Otherwise, an error is reported on that element.
+ ///
+ /// See [CompileTimeErrorCode.SUBTYPE_OF_BASE_IS_NOT_BASE_FINAL_OR_SEALED],
+ /// [CompileTimeErrorCode.SUBTYPE_OF_FINAL_IS_NOT_BASE_FINAL_OR_SEALED],
+ /// [CompileTimeErrorCode.BASE_CLASS_IMPLEMENTED_OUTSIDE_OF_LIBRARY].
+ void checkElement(
+ ClassOrMixinElementImpl element, ImplementsClause? implementsClause) {
+ final supertype = element.supertype;
+ if (supertype != null && _checkSupertypes([supertype], element)) {
+ return;
+ }
+ if (implementsClause != null &&
+ _checkInterfaceSupertypes(implementsClause.interfaces, element,
+ areImplementedInterfaces: true)) {
+ return;
+ }
+ if (_checkSupertypes(element.mixins, element)) {
+ return;
+ }
+ if (element is MixinElementImpl &&
+ _checkSupertypes(element.superclassConstraints, element,
+ areSuperclassConstraints: true)) {
+ return;
+ }
+ }
+
+ /// Returns true if a 'base' or 'final' subtype modifier error is reported for
+ /// an interface in [interfaces].
+ bool _checkInterfaceSupertypes(
+ List<NamedType> interfaces, ClassOrMixinElementImpl subElement,
+ {bool areImplementedInterfaces = false}) {
+ for (NamedType interface in interfaces) {
+ final interfaceType = interface.type;
+ if (interfaceType is InterfaceType) {
+ final interfaceElement = interfaceType.element;
+ if (interfaceElement is ClassOrMixinElementImpl) {
+ // Return early if an error has been reported to prevent reporting
+ // multiple errors on one element.
+ if (_reportRestrictionError(subElement, interfaceElement,
+ implementsNamedType: interface)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /// Returns true if a 'base' or 'final' subtype modifier error is reported for
+ /// a supertype in [supertypes].
+ bool _checkSupertypes(
+ List<InterfaceType> supertypes, ClassOrMixinElementImpl subElement,
+ {bool areSuperclassConstraints = false}) {
+ for (final supertype in supertypes) {
+ final supertypeElement = supertype.element;
+ if (supertypeElement is ClassOrMixinElementImpl) {
+ // Return early if an error has been reported to prevent reporting
+ // multiple errors on one element.
+ if (_reportRestrictionError(subElement, supertypeElement,
+ isSuperclassConstraint: areSuperclassConstraints)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /// Returns the nearest explicitly declared 'base' or 'final' element in the
+ /// element hierarchy for [element].
+ ClassOrMixinElementImpl? _getExplicitlyBaseOrFinalElement(
+ ClassOrMixinElementImpl element) {
+ // The current element has an explicit 'base' or 'final' modifier.
+ if ((element.isBase || element.isFinal) && !element.isSealed) {
+ return element;
+ }
+
+ ClassOrMixinElementImpl? baseOrFinalSuperElement;
+ final supertype = element.supertype;
+ if (supertype != null) {
+ baseOrFinalSuperElement ??=
+ _getExplicitlyBaseOrFinalElementFromSuperTypes([supertype]);
+ }
+ baseOrFinalSuperElement ??=
+ _getExplicitlyBaseOrFinalElementFromSuperTypes(element.interfaces);
+ baseOrFinalSuperElement ??=
+ _getExplicitlyBaseOrFinalElementFromSuperTypes(element.mixins);
+ if (element is MixinElementImpl) {
+ baseOrFinalSuperElement ??=
+ _getExplicitlyBaseOrFinalElementFromSuperTypes(
+ element.superclassConstraints);
+ }
+ return baseOrFinalSuperElement;
+ }
+
+ /// Returns the first explicitly declared 'base' or 'final' element found in
+ /// the class hierarchies of a supertype in [supertypes], or `null` if there
+ /// is none.
+ ClassOrMixinElementImpl? _getExplicitlyBaseOrFinalElementFromSuperTypes(
+ List<InterfaceType> supertypes) {
+ ClassOrMixinElementImpl? baseOrFinalElement;
+ for (final supertype in supertypes) {
+ final supertypeElement = supertype.element;
+ if (supertypeElement is ClassOrMixinElementImpl) {
+ baseOrFinalElement = _getExplicitlyBaseOrFinalElement(supertypeElement);
+ if (baseOrFinalElement != null) {
+ return baseOrFinalElement;
+ }
+ }
+ }
+ return baseOrFinalElement;
+ }
+
+ /// Checks whether a `final`, `base` or `interface` modifier can be ignored.
+ ///
+ /// Checks whether a subclass in the current library
+ /// can ignore a class modifier of a declaration in [superLibrary].
+ ///
+ /// Only true if the supertype library is a platform library, and
+ /// either the current library is also a platform library,
+ /// or the current library has a language version which predates
+ /// class modifiers.
+ bool _mayIgnoreClassModifiers(LibraryElement superLibrary) {
+ // Only modifiers in platform libraries can be ignored.
+ if (!superLibrary.isInSdk) return false;
+
+ // Other platform libraries can ignore modifiers.
+ if (_definingLibrary.isInSdk) return true;
+
+ // Libraries predating class modifiers can ignore platform modifiers.
+ return !_definingLibrary.featureSet.isEnabled(Feature.class_modifiers);
+ }
+
+ /// Returns true if a element modifier restriction error has been reported.
+ ///
+ /// Reports an error based on the modifier of the [superElement].
+ bool _reportRestrictionError(
+ ClassOrMixinElementImpl element, ClassOrMixinElementImpl superElement,
+ {bool isSuperclassConstraint = false, NamedType? implementsNamedType}) {
+ ClassOrMixinElementImpl? baseOrFinalSuperElement;
+ if (superElement.isBase || superElement.isFinal) {
+ // The 'base' or 'final' modifier may be an induced modifier. Find the
+ // explicitly declared 'base' or 'final' in the hierarchy.
+ baseOrFinalSuperElement = _getExplicitlyBaseOrFinalElement(superElement);
+ } else {
+ // There are no restrictions on this element's modifiers.
+ return false;
+ }
+
+ if (element.library != _definingLibrary) {
+ // Only report errors on elements within the current library.
+ return false;
+ }
+ if (baseOrFinalSuperElement != null &&
+ !_mayIgnoreClassModifiers(baseOrFinalSuperElement.library)) {
+ // The context message links to the explicitly declared 'base' or 'final'
+ // super element and is only added onto the error if 'base' or 'final' is
+ // an induced modifier of the direct super element.
+ final contextMessage = <DiagnosticMessage>[
+ DiagnosticMessageImpl(
+ filePath: baseOrFinalSuperElement.source.fullName,
+ length: baseOrFinalSuperElement.nameLength,
+ message: "The type '${superElement.displayName}' is a subtype of "
+ "'${baseOrFinalSuperElement.displayName}', and "
+ "'${baseOrFinalSuperElement.displayName}' is defined here.",
+ offset: baseOrFinalSuperElement.nameOffset,
+ url: null,
+ )
+ ];
+
+ // It's an error to implement a class if it has a supertype from a
+ // different library which is marked base.
+ if (implementsNamedType != null &&
+ superElement.isSealed &&
+ baseOrFinalSuperElement.library != element.library) {
+ if (baseOrFinalSuperElement.isBase) {
+ _errorReporter.reportErrorForNode(
+ CompileTimeErrorCode.BASE_CLASS_IMPLEMENTED_OUTSIDE_OF_LIBRARY,
+ implementsNamedType,
+ [baseOrFinalSuperElement.displayName],
+ contextMessage);
+ return true;
+ }
+ }
+
+ if (!element.isBase && !element.isFinal && !element.isSealed) {
+ if (baseOrFinalSuperElement.isFinal) {
+ if (!isSuperclassConstraint &&
+ baseOrFinalSuperElement.library != element.library) {
+ // If you can't extend, implement or mix in a final element outside of
+ // its library anyways, it's not helpful to report a subelement
+ // modifier error.
+ return false;
+ }
+ _errorReporter.reportErrorForElement(
+ CompileTimeErrorCode.SUBTYPE_OF_FINAL_IS_NOT_BASE_FINAL_OR_SEALED,
+ element,
+ [element.displayName, baseOrFinalSuperElement.displayName],
+ superElement.isSealed ? contextMessage : null);
+ return true;
+ } else if (baseOrFinalSuperElement.isBase) {
+ _errorReporter.reportErrorForElement(
+ CompileTimeErrorCode.SUBTYPE_OF_BASE_IS_NOT_BASE_FINAL_OR_SEALED,
+ element,
+ [element.displayName, baseOrFinalSuperElement.displayName],
+ superElement.isSealed ? contextMessage : null);
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/analyzer/lib/src/error/best_practices_verifier.dart b/analyzer/lib/src/error/best_practices_verifier.dart
index 54d0b14..44546be 100644
--- a/analyzer/lib/src/error/best_practices_verifier.dart
+++ b/analyzer/lib/src/error/best_practices_verifier.dart
@@ -12,6 +12,7 @@
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer/dart/element/type.dart';
+import 'package:analyzer/error/error.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/ast/extensions.dart';
@@ -291,8 +292,9 @@
}
} else {
// Something other than a declaration was annotated. Whatever this is,
- // it probably warrants a Hint, but this has not been specified on
- // visibleForTemplate or visibleForTesting, so leave it alone for now.
+ // it probably warrants a Warning, but this has not been specified on
+ // `visibleForTemplate` or `visibleForTesting`, so leave it alone for
+ // now.
}
}
@@ -312,7 +314,7 @@
? undefinedParam.value
: undefinedParam.staticParameterElement?.name;
_errorReporter.reportErrorForNode(
- HintCode.UNDEFINED_REFERENCED_PARAMETER,
+ WarningCode.UNDEFINED_REFERENCED_PARAMETER,
undefinedParam,
[paramName ?? undefinedParam, name]);
}
@@ -379,6 +381,20 @@
}
@override
+ void visitCastPattern(CastPattern node) {
+ var type = node.type.type;
+ var matchedValueType = node.matchedValueType;
+ if (type != null &&
+ _typeSystem.isNonNullable(type) &&
+ matchedValueType != null &&
+ matchedValueType.isDartCoreNull) {
+ _errorReporter.reportErrorForNode(
+ WarningCode.CAST_FROM_NULL_ALWAYS_FAILS, node);
+ }
+ super.visitCastPattern(node);
+ }
+
+ @override
void visitCatchClause(CatchClause node) {
super.visitCatchClause(node);
_checkForNullableTypeInCatchClause(node);
@@ -397,8 +413,6 @@
}
try {
- // Commented out until we decide that we want this hint in the analyzer
- // checkForOverrideEqualsButNotHashCode(node);
_checkForImmutable(node);
_checkForInvalidSealedSuperclass(node);
super.visitClassDeclaration(node);
@@ -429,11 +443,22 @@
}
@override
+ void visitConstantPattern(ConstantPattern node) {
+ if (node.expression.isDoubleNan) {
+ _errorReporter.reportErrorForNode(
+ WarningCode.UNNECESSARY_NAN_COMPARISON_FALSE,
+ node,
+ );
+ }
+ super.visitConstantPattern(node);
+ }
+
+ @override
void visitConstructorDeclaration(ConstructorDeclaration node) {
var element = node.declaredElement as ConstructorElementImpl;
if (!_isNonNullableByDefault && element.isFactory) {
if (node.body is BlockFunctionBody) {
- // Check the block for a return statement, if not, create the hint.
+ // Check the block for a return statement.
if (!ExitDetector.exits(node.body)) {
_errorReporter.reportErrorForNode(
WarningCode.MISSING_RETURN, node, [node.returnType.name]);
@@ -476,6 +501,18 @@
}
@override
+ void visitEnumDeclaration(EnumDeclaration node) {
+ _deprecatedVerifier
+ .pushInDeprecatedValue(node.declaredElement!.hasDeprecated);
+
+ try {
+ super.visitEnumDeclaration(node);
+ } finally {
+ _deprecatedVerifier.popInDeprecated();
+ }
+ }
+
+ @override
void visitExportDirective(ExportDirective node) {
_deprecatedVerifier.exportDirective(node);
_checkForInternalExport(node);
@@ -491,6 +528,18 @@
}
@override
+ void visitExtensionDeclaration(ExtensionDeclaration node) {
+ _deprecatedVerifier
+ .pushInDeprecatedValue(node.declaredElement!.hasDeprecated);
+
+ try {
+ super.visitExtensionDeclaration(node);
+ } finally {
+ _deprecatedVerifier.popInDeprecated();
+ }
+ }
+
+ @override
void visitFieldDeclaration(FieldDeclaration node) {
_deprecatedVerifier.pushInDeprecatedMetadata(node.metadata);
@@ -679,8 +728,6 @@
_inDoNotStoreMember = true;
}
try {
- // This was determined to not be a good hint, see: dartbug.com/16029
- //checkForOverridingPrivateMember(node);
_checkForMissingReturn(node.body, node);
_mustCallSuperVerifier.checkMethodDeclaration(node);
_checkForUnnecessaryNoSuchMethod(node);
@@ -725,7 +772,7 @@
@override
void visitMethodInvocation(MethodInvocation node) {
_deprecatedVerifier.methodInvocation(node);
- _checkForNullAwareHints(node, node.operator);
+ _checkForNullAwareWarnings(node, node.operator);
_errorHandlerVerifier.verifyMethodInvocation(node);
_nullSafeApiVerifier.methodInvocation(node);
super.visitMethodInvocation(node);
@@ -762,18 +809,25 @@
(type.isDynamic && name == 'dynamic')) &&
type.alias == null) {
_errorReporter.reportErrorForToken(
- HintCode.UNNECESSARY_QUESTION_MARK, question, [name]);
+ WarningCode.UNNECESSARY_QUESTION_MARK, question, [name]);
}
}
super.visitNamedType(node);
}
@override
+ void visitPatternField(PatternField node) {
+ _invalidAccessVerifier.verifyPatternField(node as PatternFieldImpl);
+ super.visitPatternField(node);
+ }
+
+ @override
void visitPostfixExpression(PostfixExpression node) {
_deprecatedVerifier.postfixExpression(node);
if (node.operator.type == TokenType.BANG &&
node.operand.typeOrThrow.isDartCoreNull) {
- _errorReporter.reportErrorForNode(HintCode.NULL_CHECK_ALWAYS_FAILS, node);
+ _errorReporter.reportErrorForNode(
+ WarningCode.NULL_CHECK_ALWAYS_FAILS, node);
}
super.visitPostfixExpression(node);
}
@@ -786,7 +840,7 @@
@override
void visitPropertyAccess(PropertyAccess node) {
- _checkForNullAwareHints(node, node.operator);
+ _checkForNullAwareWarnings(node, node.operator);
super.visitPropertyAccess(node);
}
@@ -848,14 +902,15 @@
}
}
- /// Check for the passed is expression for the unnecessary type check hint
- /// codes as well as null checks expressed using an is expression.
+ /// Checks for the passed [IsExpression] for the unnecessary type check
+ /// warning codes as well as null checks expressed using an
+ /// [IsExpression].
///
- /// @param node the is expression to check
- /// @return `true` if and only if a hint code is generated on the passed node
- /// See [HintCode.TYPE_CHECK_IS_NOT_NULL], [HintCode.TYPE_CHECK_IS_NULL],
- /// [HintCode.UNNECESSARY_TYPE_CHECK_TRUE], and
- /// [HintCode.UNNECESSARY_TYPE_CHECK_FALSE].
+ /// Returns `true` if a warning code is generated on [node].
+ /// See [WarningCode.TYPE_CHECK_IS_NOT_NULL],
+ /// [WarningCode.TYPE_CHECK_IS_NULL],
+ /// [WarningCode.UNNECESSARY_TYPE_CHECK_TRUE], and
+ /// [WarningCode.UNNECESSARY_TYPE_CHECK_FALSE].
bool _checkAllTypeChecks(IsExpression node) {
var leftNode = node.expression;
var rightNode = node.type;
@@ -864,8 +919,8 @@
void report() {
_errorReporter.reportErrorForNode(
node.notOperator == null
- ? HintCode.UNNECESSARY_TYPE_CHECK_TRUE
- : HintCode.UNNECESSARY_TYPE_CHECK_FALSE,
+ ? WarningCode.UNNECESSARY_TYPE_CHECK_TRUE
+ : WarningCode.UNNECESSARY_TYPE_CHECK_FALSE,
node,
);
}
@@ -928,7 +983,7 @@
// named elements, so we can safely assume `entry.value.name` is
// non-`null`.
_errorReporter.reportErrorForNode(
- HintCode.ASSIGNMENT_OF_DO_NOT_STORE,
+ WarningCode.ASSIGNMENT_OF_DO_NOT_STORE,
entry.key,
[entry.value.name!],
);
@@ -1004,7 +1059,7 @@
///
/// If [node] is marked with [immutable] or inherits from a class or mixin
/// marked with [immutable], this function searches the fields of [node] and
- /// its superclasses, reporting a hint if any non-final instance fields are
+ /// its superclasses, reporting a warning if any non-final instance fields are
/// found.
void _checkForImmutable(NamedCompilationUnitMember node) {
/// Return `true` if the given class [element] is annotated with the
@@ -1200,12 +1255,14 @@
element.superclassConstraints.contains(supertype)) {
// This is a special violation of the sealed class contract,
// requiring specific messaging.
- _errorReporter.reportErrorForNode(HintCode.MIXIN_ON_SEALED_CLASS,
+ _errorReporter.reportErrorForNode(WarningCode.MIXIN_ON_SEALED_CLASS,
node, [superclass.name.toString()]);
} else {
// This is a regular violation of the sealed class contract.
- _errorReporter.reportErrorForNode(HintCode.SUBTYPE_OF_SEALED_CLASS,
- node, [superclass.name.toString()]);
+ _errorReporter.reportErrorForNode(
+ WarningCode.SUBTYPE_OF_SEALED_CLASS,
+ node,
+ [superclass.name.toString()]);
}
}
}
@@ -1214,7 +1271,7 @@
void _checkForInvariantNanComparison(BinaryExpression node) {
void reportStartEnd(
- HintCode errorCode,
+ ErrorCode errorCode,
SyntacticEntity startEntity,
SyntacticEntity endEntity,
) {
@@ -1226,23 +1283,18 @@
);
}
- bool isDoubleNan(Expression expression) =>
- expression is PrefixedIdentifier &&
- expression.prefix.name == 'double' &&
- expression.identifier.name == 'nan';
-
- void checkLeftRight(HintCode errorCode) {
- if (isDoubleNan(node.leftOperand)) {
+ void checkLeftRight(ErrorCode errorCode) {
+ if (node.leftOperand.isDoubleNan) {
reportStartEnd(errorCode, node.leftOperand, node.operator);
- } else if (isDoubleNan(node.rightOperand)) {
+ } else if (node.rightOperand.isDoubleNan) {
reportStartEnd(errorCode, node.operator, node.rightOperand);
}
}
if (node.operator.type == TokenType.BANG_EQ) {
- checkLeftRight(HintCode.UNNECESSARY_NAN_COMPARISON_TRUE);
+ checkLeftRight(WarningCode.UNNECESSARY_NAN_COMPARISON_TRUE);
} else if (node.operator.type == TokenType.EQ_EQ) {
- checkLeftRight(HintCode.UNNECESSARY_NAN_COMPARISON_FALSE);
+ checkLeftRight(WarningCode.UNNECESSARY_NAN_COMPARISON_FALSE);
}
}
@@ -1250,7 +1302,7 @@
if (!_isNonNullableByDefault) return;
void reportStartEnd(
- HintCode errorCode,
+ ErrorCode errorCode,
SyntacticEntity startEntity,
SyntacticEntity endEntity,
) {
@@ -1262,7 +1314,7 @@
);
}
- void checkLeftRight(HintCode errorCode) {
+ void checkLeftRight(ErrorCode errorCode) {
if (node.leftOperand is NullLiteral) {
var rightType = node.rightOperand.typeOrThrow;
if (_typeSystem.isStrictlyNonNullable(rightType)) {
@@ -1279,9 +1331,9 @@
}
if (node.operator.type == TokenType.BANG_EQ) {
- checkLeftRight(HintCode.UNNECESSARY_NULL_COMPARISON_TRUE);
+ checkLeftRight(WarningCode.UNNECESSARY_NULL_COMPARISON_TRUE);
} else if (node.operator.type == TokenType.EQ_EQ) {
- checkLeftRight(HintCode.UNNECESSARY_NULL_COMPARISON_FALSE);
+ checkLeftRight(WarningCode.UNNECESSARY_NULL_COMPARISON_FALSE);
}
}
@@ -1304,10 +1356,10 @@
if (constructorName.name != null) {
fullConstructorName = '$fullConstructorName.${constructorName.name}';
}
- HintCode hint = node.keyword?.keyword == Keyword.NEW
- ? HintCode.NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR_USING_NEW
- : HintCode.NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR;
- _errorReporter.reportErrorForNode(hint, node, [fullConstructorName]);
+ var warning = node.keyword?.keyword == Keyword.NEW
+ ? WarningCode.NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR_USING_NEW
+ : WarningCode.NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR;
+ _errorReporter.reportErrorForNode(warning, node, [fullConstructorName]);
}
}
@@ -1337,13 +1389,10 @@
return false;
}
- /// Generate a hint for functions or methods that have a return type, but do
- /// not have a return statement on all branches. At the end of blocks with no
- /// return, Dart implicitly returns `null`. Avoiding these implicit returns
- /// is considered a best practice.
- ///
- /// Note: for async functions/methods, this hint only applies when the
- /// function has a return type that Future<Null> is not assignable to.
+ /// Generates a warning for functions that have a potentially non-nullable
+ /// return type, but do not have a return statement on all branches. At the
+ /// end of blocks with no return, Dart implicitly returns `null`. Avoiding
+ /// these implicit returns is considered a best practice.
///
/// See [WarningCode.MISSING_RETURN].
void _checkForMissingReturn(FunctionBody body, AstNode functionNode) {
@@ -1411,8 +1460,8 @@
}
}
- /// Produce several null-aware related hints.
- void _checkForNullAwareHints(Expression node, Token? operator) {
+ /// Produce several null-aware related warnings.
+ void _checkForNullAwareWarnings(Expression node, Token? operator) {
if (_isNonNullableByDefault) {
return;
}
@@ -1511,10 +1560,10 @@
}
}
- /// Generate a hint for `noSuchMethod` methods that do nothing except of
- /// calling another `noSuchMethod` that is not defined by `Object`.
+ /// Generates a warning for `noSuchMethod` methods that do nothing except of
+ /// calling another `noSuchMethod` which is not defined by `Object`.
///
- /// Return `true` if and only if a hint code is generated on the passed node.
+ /// Returns `true` if a warning code is generated for [node].
bool _checkForUnnecessaryNoSuchMethod(MethodDeclaration node) {
if (node.name.lexeme != FunctionElement.NO_SUCH_METHOD_METHOD_NAME) {
return false;
@@ -1539,7 +1588,7 @@
if (body is ExpressionFunctionBody) {
if (isNonObjectNoSuchMethodInvocation(body.expression)) {
_errorReporter.reportErrorForToken(
- HintCode.UNNECESSARY_NO_SUCH_METHOD, node.name);
+ WarningCode.UNNECESSARY_NO_SUCH_METHOD, node.name);
return true;
}
} else if (body is BlockFunctionBody) {
@@ -1549,7 +1598,7 @@
if (returnStatement is ReturnStatement &&
isNonObjectNoSuchMethodInvocation(returnStatement.expression)) {
_errorReporter.reportErrorForToken(
- HintCode.UNNECESSARY_NO_SUCH_METHOD, node.name);
+ WarningCode.UNNECESSARY_NO_SUCH_METHOD, node.name);
return true;
}
}
@@ -1576,12 +1625,13 @@
if (returnType == null) return;
bool isReturnVoid;
- if (returnType.isVoid) {
+ if (returnType is VoidType) {
isReturnVoid = true;
} else if (returnType is ParameterizedType &&
(returnType.isDartAsyncFuture || returnType.isDartAsyncFutureOr)) {
var typeArguments = returnType.typeArguments;
- isReturnVoid = typeArguments.length == 1 && typeArguments.first.isVoid;
+ isReturnVoid =
+ typeArguments.length == 1 && typeArguments.first is VoidType;
} else {
isReturnVoid = false;
}
@@ -1591,7 +1641,7 @@
var elements = expression.elements;
if (elements.length == 1 && elements.first is Expression) {
_errorReporter.reportErrorForNode(
- HintCode.UNNECESSARY_SET_LITERAL, expression);
+ WarningCode.UNNECESSARY_SET_LITERAL, expression);
}
}
}
@@ -1959,18 +2009,18 @@
: _inTemplateSource =
_library.source.fullName.contains(_templateExtension);
- /// Produces a hint if [identifier] is accessed from an invalid location.
+ /// Produces a warning if [identifier] is accessed from an invalid location.
///
- /// In particular, a hint is produced in either of the two following cases:
+ /// In particular, a warning is produced in either of the two following cases:
///
/// * The element associated with [identifier] is annotated with [internal],
/// and is accessed from outside the package in which the element is
/// declared.
/// * The element associated with [identifier] is annotated with [protected],
- /// [visibleForTesting], and/or [visibleForTemplate], and is accessed from a
+ /// [visibleForTesting], and/or `visibleForTemplate`, and is accessed from a
/// location which is invalid as per the rules of each such annotation.
/// Conversely, if the element is annotated with more than one of these
- /// annotations, the access is valid (and no hint will be produced) if it
+ /// annotations, the access is valid (and no warning is produced) if it
/// conforms to the rules of at least one of the annotations.
void verify(SimpleIdentifier identifier) {
if (identifier.inDeclarationContext() || _inCommentReference(identifier)) {
@@ -2035,6 +2085,30 @@
}
}
+ void verifyPatternField(PatternFieldImpl node) {
+ var element = node.element;
+ if (element == null || _inCurrentLibrary(element)) {
+ return;
+ }
+
+ if (_hasInternal(element) &&
+ !_isLibraryInWorkspacePackage(element.library)) {
+ var fieldName = node.name;
+ if (fieldName == null) {
+ return;
+ }
+ var errorEntity = node.errorEntity;
+
+ _errorReporter.reportErrorForOffset(
+ WarningCode.INVALID_USE_OF_INTERNAL_MEMBER,
+ errorEntity.offset,
+ errorEntity.length,
+ [element.displayName]);
+ }
+
+ _checkForOtherInvalidAccess(node, element);
+ }
+
void verifySuperConstructorInvocation(SuperConstructorInvocation node) {
if (node.constructorName != null) {
// Named constructor calls are handled by [verify].
@@ -2048,8 +2122,7 @@
}
}
- void _checkForInvalidInternalAccess(
- SimpleIdentifier identifier, Element element) {
+ void _checkForInvalidInternalAccess(Identifier identifier, Element element) {
if (_hasInternal(element) &&
!_isLibraryInWorkspacePackage(element.library)) {
String name;
@@ -2070,8 +2143,7 @@
}
}
- void _checkForOtherInvalidAccess(
- SimpleIdentifier identifier, Element element) {
+ void _checkForOtherInvalidAccess(AstNode node, Element element) {
bool hasProtected = _hasProtected(element);
if (hasProtected) {
var definingClass = element.enclosingElement as InterfaceElement;
@@ -2080,16 +2152,16 @@
}
}
- bool hasVisibleForTemplate = _hasVisibleForTemplate(element);
- if (hasVisibleForTemplate) {
- if (_inTemplateSource || _inExportDirective(identifier)) {
+ bool isVisibleForTemplateApplied = _isVisibleForTemplateApplied(element);
+ if (isVisibleForTemplateApplied) {
+ if (_inTemplateSource || _inExportDirective(node)) {
return;
}
}
bool hasVisibleForTesting = _hasVisibleForTesting(element);
if (hasVisibleForTesting) {
- if (_inTestDirectory || _inExportDirective(identifier)) {
+ if (_inTestDirectory || _inExportDirective(node)) {
return;
}
}
@@ -2101,37 +2173,49 @@
// annotation present.
String name;
- AstNode node;
+ SyntacticEntity errorEntity = node;
- var grandparent = identifier.parent?.parent;
-
- if (grandparent is ConstructorName) {
- name = grandparent.toSource();
- node = grandparent;
+ var grandparent = node.parent?.parent;
+ if (node is Identifier) {
+ if (grandparent is ConstructorName) {
+ name = grandparent.toSource();
+ errorEntity = grandparent;
+ } else {
+ name = node.name;
+ }
+ } else if (node is PatternFieldImpl) {
+ name = element.displayName;
+ errorEntity = node.errorEntity;
} else {
- name = identifier.name;
- node = identifier;
+ throw StateError('Can only handle Identifier or PatternField, but got '
+ '${node.runtimeType}');
}
var definingClass = element.enclosingElement;
- if (hasProtected) {
- _errorReporter.reportErrorForNode(
- WarningCode.INVALID_USE_OF_PROTECTED_MEMBER,
- node,
- [name, definingClass!.source!.uri]);
+ if (definingClass == null) {
+ return;
}
- if (hasVisibleForTemplate) {
- _errorReporter.reportErrorForNode(
+ if (hasProtected) {
+ _errorReporter.reportErrorForOffset(
+ WarningCode.INVALID_USE_OF_PROTECTED_MEMBER,
+ errorEntity.offset,
+ errorEntity.length,
+ [name, definingClass.source!.uri]);
+ }
+ if (isVisibleForTemplateApplied) {
+ _errorReporter.reportErrorForOffset(
WarningCode.INVALID_USE_OF_VISIBLE_FOR_TEMPLATE_MEMBER,
- node,
- [name, definingClass!.source!.uri]);
+ errorEntity.offset,
+ errorEntity.length,
+ [name, definingClass.source!.uri]);
}
if (hasVisibleForTesting) {
- _errorReporter.reportErrorForNode(
+ _errorReporter.reportErrorForOffset(
WarningCode.INVALID_USE_OF_VISIBLE_FOR_TESTING_MEMBER,
- node,
- [name, definingClass!.source!.uri]);
+ errorEntity.offset,
+ errorEntity.length,
+ [name, definingClass.source!.uri]);
}
if (hasVisibleForOverriding) {
@@ -2141,14 +2225,15 @@
parent is PropertyAccess && parent.target is SuperExpression) {
var methodDeclaration =
grandparent?.thisOrAncestorOfType<MethodDeclaration>();
- if (methodDeclaration?.name.lexeme == identifier.name) {
+ if (methodDeclaration?.name.lexeme == name) {
validOverride = true;
}
}
if (!validOverride) {
- _errorReporter.reportErrorForNode(
+ _errorReporter.reportErrorForOffset(
WarningCode.INVALID_USE_OF_VISIBLE_FOR_OVERRIDING_MEMBER,
- node,
+ errorEntity.offset,
+ errorEntity.length,
[name]);
}
}
@@ -2215,6 +2300,10 @@
element.variable.hasVisibleForTemplate) {
return true;
}
+ final enclosingElement = element.enclosingElement;
+ if (_hasVisibleForTemplate(enclosingElement)) {
+ return true;
+ }
return false;
}
@@ -2236,9 +2325,8 @@
bool _inCurrentLibrary(Element element) => element.library == _library;
- bool _inExportDirective(SimpleIdentifier identifier) =>
- identifier.parent is Combinator &&
- identifier.parent!.parent is ExportDirective;
+ bool _inExportDirective(AstNode node) =>
+ node.parent is Combinator && node.parent!.parent is ExportDirective;
bool _isLibraryInWorkspacePackage(LibraryElement? library) {
if (_workspacePackage == null || library == null) {
@@ -2248,6 +2336,21 @@
}
return _workspacePackage!.contains(library.source);
}
+
+ /// Check if @visibleForTemplate is applied to the given [Element].
+ ///
+ /// [ClassElement] and [EnumElement] are excluded from the @visibleForTemplate
+ /// access checks. Instead, the access restriction is cascaded to the
+ /// corresponding class members and enum constants. For other types of
+ /// elements, check if they are annotated based on `hasVisibleForTemplate`
+ /// value.
+ bool _isVisibleForTemplateApplied(Element? element) {
+ if (element is ClassElement || element is EnumElement) {
+ return false;
+ } else {
+ return _hasVisibleForTemplate(element);
+ }
+ }
}
/// A visitor that determines, upon visiting a function body and/or a
@@ -2273,3 +2376,15 @@
}
}
}
+
+extension on Expression {
+ /// Whether this is the [PrefixedIdentifier] referring to `double.nan`.
+ // TODO(srawlins): This will return the wrong answer for `prefixed.double.nan`
+ // and for `import 'foo.dart' as double; double.nan`.
+ bool get isDoubleNan {
+ final self = this;
+ return self is PrefixedIdentifier &&
+ self.prefix.name == 'double' &&
+ self.identifier.name == 'nan';
+ }
+}
diff --git a/analyzer/lib/src/error/codes.g.dart b/analyzer/lib/src/error/codes.g.dart
index 220cacc..0369aa1 100644
--- a/analyzer/lib/src/error/codes.g.dart
+++ b/analyzer/lib/src/error/codes.g.dart
@@ -415,8 +415,8 @@
/// 0: the name of the class being used as a mixin
static const CompileTimeErrorCode CLASS_USED_AS_MIXIN = CompileTimeErrorCode(
'CLASS_USED_AS_MIXIN',
- "The class '{0}' can't be used as a mixin because it isn't a mixin class "
- "nor a mixin.",
+ "The class '{0}' can't be used as a mixin because it's neither a mixin "
+ "class nor a mixin.",
);
static const CompileTimeErrorCode CONCRETE_CLASS_HAS_ENUM_SUPERINTERFACE =
@@ -2140,18 +2140,6 @@
);
/// Parameters:
- /// 0: the name of the pattern variable
- static const CompileTimeErrorCode
- INCONSISTENT_PATTERN_VARIABLE_SHARED_CASE_SCOPE = CompileTimeErrorCode(
- 'INCONSISTENT_PATTERN_VARIABLE_SHARED_CASE_SCOPE',
- "The variable '{0}' doesn't have the same type and/or finality in all "
- "cases that share this body.",
- correctionMessage:
- "Try declaring the variable pattern with the same type and finality in "
- "all cases.",
- );
-
- /// Parameters:
/// 0: the name of the initializing formal that is not an instance variable in
/// the immediately enclosing class
static const CompileTimeErrorCode INITIALIZER_FOR_NON_EXISTENT_FIELD =
@@ -3016,6 +3004,15 @@
);
/// Parameters:
+ /// 0: the name of the mixin class that is invalid
+ static const CompileTimeErrorCode MIXIN_CLASS_DECLARATION_EXTENDS_NOT_OBJECT =
+ CompileTimeErrorCode(
+ 'MIXIN_CLASS_DECLARATION_EXTENDS_NOT_OBJECT',
+ "The class '{0}' can't be declared a mixin because it extends a class "
+ "other than 'Object'.",
+ );
+
+ /// Parameters:
/// 0: the name of the mixin that is invalid
static const CompileTimeErrorCode MIXIN_CLASS_DECLARES_CONSTRUCTOR =
CompileTimeErrorCode(
@@ -3470,7 +3467,7 @@
CompileTimeErrorCode(
'NON_EXHAUSTIVE_SWITCH',
"The type '{0}' is not exhaustively matched by the switch cases.",
- correctionMessage: "Try adding a default case or cases that match {1}.",
+ correctionMessage: "Try adding a default case or cases that match '{1}'.",
);
/// No parameters.
@@ -3876,11 +3873,11 @@
hasPublishedDocs: true,
);
+ /// No parameters.
static const CompileTimeErrorCode PATTERN_ASSIGNMENT_NOT_LOCAL_VARIABLE =
CompileTimeErrorCode(
'PATTERN_ASSIGNMENT_NOT_LOCAL_VARIABLE',
- "Only local variables or formal parameters can be used in pattern "
- "assignments.",
+ "Only local variables can be assigned in pattern assignments.",
correctionMessage: "Try assigning to a local variable.",
);
@@ -3913,6 +3910,46 @@
correctionMessage: "Try assigning to a different variable.",
);
+ /// Parameters:
+ /// 0: the name of the pattern variable
+ static const CompileTimeErrorCode
+ PATTERN_VARIABLE_SHARED_CASE_SCOPE_DIFFERENT_FINALITY_OR_TYPE =
+ CompileTimeErrorCode(
+ 'INVALID_PATTERN_VARIABLE_IN_SHARED_CASE_SCOPE',
+ "The variable '{0}' doesn't have the same type and/or finality in all "
+ "cases that share this body.",
+ correctionMessage:
+ "Try declaring the variable pattern with the same type and finality in "
+ "all cases.",
+ uniqueName: 'PATTERN_VARIABLE_SHARED_CASE_SCOPE_DIFFERENT_FINALITY_OR_TYPE',
+ );
+
+ /// Parameters:
+ /// 0: the name of the pattern variable
+ static const CompileTimeErrorCode
+ PATTERN_VARIABLE_SHARED_CASE_SCOPE_HAS_LABEL = CompileTimeErrorCode(
+ 'INVALID_PATTERN_VARIABLE_IN_SHARED_CASE_SCOPE',
+ "The variable '{0}' is not available because there is a label or 'default' "
+ "case.",
+ correctionMessage:
+ "Try removing the label, or providing the 'default' case with its own "
+ "body.",
+ uniqueName: 'PATTERN_VARIABLE_SHARED_CASE_SCOPE_HAS_LABEL',
+ );
+
+ /// Parameters:
+ /// 0: the name of the pattern variable
+ static const CompileTimeErrorCode
+ PATTERN_VARIABLE_SHARED_CASE_SCOPE_NOT_ALL_CASES = CompileTimeErrorCode(
+ 'INVALID_PATTERN_VARIABLE_IN_SHARED_CASE_SCOPE',
+ "The variable '{0}' is available in some, but not all cases that share "
+ "this body.",
+ correctionMessage:
+ "Try declaring the variable pattern with the same type and finality in "
+ "all cases.",
+ uniqueName: 'PATTERN_VARIABLE_SHARED_CASE_SCOPE_NOT_ALL_CASES',
+ );
+
/// No parameters.
static const CompileTimeErrorCode
POSITIONAL_SUPER_FORMAL_PARAMETER_WITH_POSITIONAL_ARGUMENT =
@@ -4240,6 +4277,17 @@
"instead.",
);
+ /// Parameters:
+ /// 0: the operand type
+ /// 1: the parameter type of the invoked operator
+ /// 2: the name of the invoked operator
+ static const CompileTimeErrorCode
+ RELATIONAL_PATTERN_OPERAND_TYPE_NOT_ASSIGNABLE = CompileTimeErrorCode(
+ 'RELATIONAL_PATTERN_OPERAND_TYPE_NOT_ASSIGNABLE',
+ "The constant expression type '{0}' is not assignable to the parameter "
+ "type '{1}' of the '{2}' operator.",
+ );
+
static const CompileTimeErrorCode
RELATIONAL_PATTERN_OPERATOR_RETURN_TYPE_NOT_ASSIGNABLE_TO_BOOL =
CompileTimeErrorCode(
@@ -4423,6 +4471,28 @@
);
/// Parameters:
+ /// 0: the name of the subtype that is not 'base', 'final', or 'sealed'
+ /// 1: the name of the 'base' supertype
+ static const CompileTimeErrorCode
+ SUBTYPE_OF_BASE_IS_NOT_BASE_FINAL_OR_SEALED = CompileTimeErrorCode(
+ 'SUBTYPE_OF_BASE_OR_FINAL_IS_NOT_BASE_FINAL_OR_SEALED',
+ "The type '{0}' must be 'base', 'final' or 'sealed' because the supertype "
+ "'{1}' is 'base'.",
+ uniqueName: 'SUBTYPE_OF_BASE_IS_NOT_BASE_FINAL_OR_SEALED',
+ );
+
+ /// Parameters:
+ /// 0: the name of the subtype that is not 'base', 'final', or 'sealed'
+ /// 1: the name of the 'final' supertype
+ static const CompileTimeErrorCode
+ SUBTYPE_OF_FINAL_IS_NOT_BASE_FINAL_OR_SEALED = CompileTimeErrorCode(
+ 'SUBTYPE_OF_BASE_OR_FINAL_IS_NOT_BASE_FINAL_OR_SEALED',
+ "The type '{0}' must be 'base', 'final' or 'sealed' because the supertype "
+ "'{1}' is 'final'.",
+ uniqueName: 'SUBTYPE_OF_FINAL_IS_NOT_BASE_FINAL_OR_SEALED',
+ );
+
+ /// Parameters:
/// 0: the type of super-parameter
/// 1: the type of associated super-constructor parameter
static const CompileTimeErrorCode
@@ -5181,6 +5251,7 @@
hasPublishedDocs: true,
);
+ /// No parameters.
static const CompileTimeErrorCode
VARIABLE_PATTERN_KEYWORD_IN_DECLARATION_CONTEXT = CompileTimeErrorCode(
'VARIABLE_PATTERN_KEYWORD_IN_DECLARATION_CONTEXT',
@@ -5691,6 +5762,18 @@
hasPublishedDocs: true,
);
+ /// Users should not assign values marked `@doNotStore`.
+ ///
+ /// Parameters:
+ /// 0: the name of the field or variable
+ static const WarningCode ASSIGNMENT_OF_DO_NOT_STORE = WarningCode(
+ 'ASSIGNMENT_OF_DO_NOT_STORE',
+ "'{0}' is marked 'doNotStore' and shouldn't be assigned to a field or "
+ "top-level variable.",
+ correctionMessage: "Try removing the assignment.",
+ hasPublishedDocs: true,
+ );
+
/// Parameters:
/// 0: the return type as derived by the type of the [Future].
static const WarningCode BODY_MIGHT_COMPLETE_NORMALLY_CATCH_ERROR =
@@ -5745,8 +5828,49 @@
"Try a constant of the same type as the matched value type.",
);
- /// This is the new replacement for [HintCode.DEAD_CODE].
- static const HintCode DEAD_CODE = HintCode.DEAD_CODE;
+ /// Dead code is code that is never reached, this can happen for instance if a
+ /// statement follows a return statement.
+ ///
+ /// No parameters.
+ static const WarningCode DEAD_CODE = WarningCode(
+ 'DEAD_CODE',
+ "Dead code.",
+ correctionMessage:
+ "Try removing the code, or fixing the code before it so that it can be "
+ "reached.",
+ hasPublishedDocs: true,
+ );
+
+ /// Dead code is code that is never reached. This case covers cases where the
+ /// user has catch clauses after `catch (e)` or `on Object catch (e)`.
+ ///
+ /// No parameters.
+ static const WarningCode DEAD_CODE_CATCH_FOLLOWING_CATCH = WarningCode(
+ 'DEAD_CODE_CATCH_FOLLOWING_CATCH',
+ "Dead code: Catch clauses after a 'catch (e)' or an 'on Object catch (e)' "
+ "are never reached.",
+ correctionMessage:
+ "Try reordering the catch clauses so that they can be reached, or "
+ "removing the unreachable catch clauses.",
+ hasPublishedDocs: true,
+ );
+
+ /// Dead code is code that is never reached. This case covers cases where the
+ /// user has an on-catch clause such as `on A catch (e)`, where a supertype of
+ /// `A` was already caught.
+ ///
+ /// Parameters:
+ /// 0: name of the subtype
+ /// 1: name of the supertype
+ static const WarningCode DEAD_CODE_ON_CATCH_SUBTYPE = WarningCode(
+ 'DEAD_CODE_ON_CATCH_SUBTYPE',
+ "Dead code: This on-catch block won't be executed because '{0}' is a "
+ "subtype of '{1}' and hence will have been caught already.",
+ correctionMessage:
+ "Try reordering the catch clauses so that this block can be reached, "
+ "or removing the unreachable catch clause.",
+ hasPublishedDocs: true,
+ );
/// No parameters.
static const WarningCode DEPRECATED_EXTENDS_FUNCTION = WarningCode(
@@ -5966,8 +6090,8 @@
hasPublishedDocs: true,
);
- /// This hint is generated anywhere a @factory annotation is associated with
- /// anything other than a method.
+ /// This warning is generated anywhere a @factory annotation is associated
+ /// with anything other than a method.
static const WarningCode INVALID_FACTORY_ANNOTATION = WarningCode(
'INVALID_FACTORY_ANNOTATION',
"Only methods can be annotated as factories.",
@@ -5989,8 +6113,8 @@
hasPublishedDocs: true,
);
- /// This hint is generated anywhere an @immutable annotation is associated with
- /// anything other than a class.
+ /// This warning is generated anywhere an @immutable annotation is associated
+ /// with anything other than a class.
static const WarningCode INVALID_IMMUTABLE_ANNOTATION = WarningCode(
'INVALID_IMMUTABLE_ANNOTATION',
"Only classes can be annotated as being immutable.",
@@ -6125,7 +6249,7 @@
hasPublishedDocs: true,
);
- /// This hint is generated anywhere where `@nonVirtual` annotates something
+ /// This warning is generated anywhere where `@nonVirtual` annotates something
/// other than a non-abstract instance member in a class or mixin.
///
/// No Parameters.
@@ -6137,7 +6261,7 @@
hasPublishedDocs: true,
);
- /// This hint is generated anywhere where an instance member annotated with
+ /// This warning is generated anywhere where an instance member annotated with
/// `@nonVirtual` is overridden in a subclass.
///
/// Parameters:
@@ -6150,7 +6274,7 @@
hasPublishedDocs: true,
);
- /// This hint is generated anywhere where `@required` annotates a named
+ /// This warning is generated anywhere where `@required` annotates a named
/// parameter with a default value.
///
/// Parameters:
@@ -6162,7 +6286,7 @@
correctionMessage: "Remove @required.",
);
- /// This hint is generated anywhere where `@required` annotates an optional
+ /// This warning is generated anywhere where `@required` annotates an optional
/// positional parameter.
///
/// Parameters:
@@ -6175,8 +6299,8 @@
correctionMessage: "Remove @required.",
);
- /// This hint is generated anywhere where `@required` annotates a non optional
- /// positional parameter.
+ /// This warning is generated anywhere where `@required` annotates a
+ /// non-optional positional parameter.
///
/// Parameters:
/// 0: the name of the member
@@ -6187,8 +6311,8 @@
correctionMessage: "Remove @required.",
);
- /// This hint is generated anywhere where `@sealed` annotates something other
- /// than a class.
+ /// This warning is generated anywhere where `@sealed` annotates something
+ /// other than a class.
///
/// No parameters.
static const WarningCode INVALID_SEALED_ANNOTATION = WarningCode(
@@ -6206,8 +6330,8 @@
hasPublishedDocs: true,
);
- /// This hint is generated anywhere where a member annotated with `@protected`
- /// is used outside of an instance member of a subclass.
+ /// This warning is generated anywhere where a member annotated with
+ /// `@protected` is used outside of an instance member of a subclass.
///
/// Parameters:
/// 0: the name of the member
@@ -6227,7 +6351,7 @@
hasPublishedDocs: true,
);
- /// This hint is generated anywhere where a member annotated with
+ /// This warning is generated anywhere where a member annotated with
/// `@visibleForTemplate` is used outside of a "template" Dart file.
///
/// Parameters:
@@ -6239,7 +6363,7 @@
"The member '{0}' can only be used within '{1}' or a template library.",
);
- /// This hint is generated anywhere where a member annotated with
+ /// This warning is generated anywhere where a member annotated with
/// `@visibleForTesting` is used outside the defining library, or a test.
///
/// Parameters:
@@ -6252,8 +6376,8 @@
hasPublishedDocs: true,
);
- /// This hint is generated anywhere where a private declaration is annotated
- /// with `@visibleForTemplate` or `@visibleForTesting`.
+ /// This warning is generated anywhere where a private declaration is
+ /// annotated with `@visibleForTemplate` or `@visibleForTesting`.
///
/// Parameters:
/// 0: the name of the member
@@ -6310,8 +6434,8 @@
uniqueName: 'MISSING_OVERRIDE_OF_MUST_BE_OVERRIDDEN_TWO',
);
- /// Generate a hint for a constructor, function or method invocation where a
- /// required parameter is missing.
+ /// Generates a warning for a constructor, function or method invocation where
+ /// a required parameter is missing.
///
/// Parameters:
/// 0: the name of the parameter
@@ -6321,8 +6445,8 @@
hasPublishedDocs: true,
);
- /// Generate a hint for a constructor, function or method invocation where a
- /// required parameter is missing.
+ /// Generates a warning for a constructor, function or method invocation where
+ /// a required parameter is missing.
///
/// Parameters:
/// 0: the name of the parameter
@@ -6345,7 +6469,23 @@
hasPublishedDocs: true,
);
- /// Generate a hint for classes that inherit from classes annotated with
+ /// This warning is generated anywhere where a `@sealed` class is used as a
+ /// a superclass constraint of a mixin.
+ ///
+ /// Parameters:
+ /// 0: the name of the sealed class
+ static const WarningCode MIXIN_ON_SEALED_CLASS = WarningCode(
+ 'MIXIN_ON_SEALED_CLASS',
+ "The class '{0}' shouldn't be used as a mixin constraint because it is "
+ "sealed, and any class mixing in this mixin must have '{0}' as a "
+ "superclass.",
+ correctionMessage:
+ "Try composing with this class, or refer to its documentation for more "
+ "information.",
+ hasPublishedDocs: true,
+ );
+
+ /// Generates a warning for classes that inherit from classes annotated with
/// `@immutable` but that are not immutable.
///
/// Parameters:
@@ -6367,9 +6507,33 @@
hasPublishedDocs: true,
);
- /// This is the new replacement for [HintCode.NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR].
- static const HintCode NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR =
- HintCode.NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR;
+ /// Generates a warning for non-const instance creation using a constructor
+ /// annotated with `@literal`.
+ ///
+ /// Parameters:
+ /// 0: the name of the class defining the annotated constructor
+ static const WarningCode NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR = WarningCode(
+ 'NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR',
+ "This instance creation must be 'const', because the {0} constructor is "
+ "marked as '@literal'.",
+ correctionMessage: "Try adding a 'const' keyword.",
+ hasPublishedDocs: true,
+ );
+
+ /// Generate a warning for non-const instance creation (with the `new` keyword)
+ /// using a constructor annotated with `@literal`.
+ ///
+ /// Parameters:
+ /// 0: the name of the class defining the annotated constructor
+ static const WarningCode NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR_USING_NEW =
+ WarningCode(
+ 'NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR',
+ "This instance creation must be 'const', because the {0} constructor is "
+ "marked as '@literal'.",
+ correctionMessage: "Try replacing the 'new' keyword with 'const'.",
+ hasPublishedDocs: true,
+ uniqueName: 'NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR_USING_NEW',
+ );
/// No parameters.
static const WarningCode NULLABLE_TYPE_IN_CATCH_CLAUSE = WarningCode(
@@ -6417,9 +6581,65 @@
"an operand of a logical operator.",
);
- /// This is the new replacement for [HintCode.OVERRIDE_ON_NON_OVERRIDING_FIELD].
- static const HintCode OVERRIDE_ON_NON_OVERRIDING_FIELD =
- HintCode.OVERRIDE_ON_NON_OVERRIDING_FIELD;
+ /// No parameters.
+ static const WarningCode NULL_CHECK_ALWAYS_FAILS = WarningCode(
+ 'NULL_CHECK_ALWAYS_FAILS',
+ "This null-check will always throw an exception because the expression "
+ "will always evaluate to 'null'.",
+ hasPublishedDocs: true,
+ );
+
+ /// A field with the override annotation does not override a getter or setter.
+ ///
+ /// No parameters.
+ static const WarningCode OVERRIDE_ON_NON_OVERRIDING_FIELD = WarningCode(
+ 'OVERRIDE_ON_NON_OVERRIDING_MEMBER',
+ "The field doesn't override an inherited getter or setter.",
+ correctionMessage:
+ "Try updating this class to match the superclass, or removing the "
+ "override annotation.",
+ hasPublishedDocs: true,
+ uniqueName: 'OVERRIDE_ON_NON_OVERRIDING_FIELD',
+ );
+
+ /// A getter with the override annotation does not override an existing getter.
+ ///
+ /// No parameters.
+ static const WarningCode OVERRIDE_ON_NON_OVERRIDING_GETTER = WarningCode(
+ 'OVERRIDE_ON_NON_OVERRIDING_MEMBER',
+ "The getter doesn't override an inherited getter.",
+ correctionMessage:
+ "Try updating this class to match the superclass, or removing the "
+ "override annotation.",
+ hasPublishedDocs: true,
+ uniqueName: 'OVERRIDE_ON_NON_OVERRIDING_GETTER',
+ );
+
+ /// A method with the override annotation does not override an existing method.
+ ///
+ /// No parameters.
+ static const WarningCode OVERRIDE_ON_NON_OVERRIDING_METHOD = WarningCode(
+ 'OVERRIDE_ON_NON_OVERRIDING_MEMBER',
+ "The method doesn't override an inherited method.",
+ correctionMessage:
+ "Try updating this class to match the superclass, or removing the "
+ "override annotation.",
+ hasPublishedDocs: true,
+ uniqueName: 'OVERRIDE_ON_NON_OVERRIDING_METHOD',
+ );
+
+ /// A setter with the override annotation does not override an existing setter.
+ ///
+ /// No parameters.
+ static const WarningCode OVERRIDE_ON_NON_OVERRIDING_SETTER = WarningCode(
+ 'OVERRIDE_ON_NON_OVERRIDING_MEMBER',
+ "The setter doesn't override an inherited setter.",
+ correctionMessage:
+ "Try updating this class to match the superclass, or removing the "
+ "override annotation.",
+ hasPublishedDocs: true,
+ uniqueName: 'OVERRIDE_ON_NON_OVERRIDING_SETTER',
+ );
/// It is not an error to call or tear-off a method, setter, or getter, or to
/// read or write a field, on a receiver of static type `Never`.
@@ -6440,6 +6660,14 @@
"Try checking for throw expressions or type errors in the receiver",
);
+ static const WarningCode RECORD_LITERAL_ONE_POSITIONAL_NO_TRAILING_COMMA =
+ WarningCode(
+ 'RECORD_LITERAL_ONE_POSITIONAL_NO_TRAILING_COMMA',
+ "A record literal with exactly one positional field requires a trailing "
+ "comma.",
+ correctionMessage: "Try adding a trailing comma.",
+ );
+
/// An error code indicating use of a removed lint rule.
///
/// Parameters:
@@ -6605,6 +6833,16 @@
hasPublishedDocs: true,
);
+ /// Parameters:
+ /// 0: the version specified in the `@Since()` annotation
+ /// 1: the SDK version constraints
+ static const WarningCode SDK_VERSION_SINCE = WarningCode(
+ 'SDK_VERSION_SINCE',
+ "This API is available since SDK {0}, but constraints '{1}' don't "
+ "guarantee it.",
+ correctionMessage: "Try updating the SDK constraints.",
+ );
+
/// No parameters.
static const WarningCode SDK_VERSION_UI_AS_CODE = WarningCode(
'SDK_VERSION_UI_AS_CODE',
@@ -6625,6 +6863,31 @@
hasPublishedDocs: true,
);
+ /// When "strict-raw-types" is enabled, "raw types" must have type arguments.
+ ///
+ /// A "raw type" is a type name that does not use inference to fill in missing
+ /// type arguments; instead, each type argument is instantiated to its bound.
+ ///
+ /// Parameters:
+ /// 0: the name of the generic type
+ static const WarningCode STRICT_RAW_TYPE = WarningCode(
+ 'STRICT_RAW_TYPE',
+ "The generic type '{0}' should have explicit type arguments but doesn't.",
+ correctionMessage: "Use explicit type arguments for '{0}'.",
+ );
+
+ /// Parameters:
+ /// 0: the name of the sealed class
+ static const WarningCode SUBTYPE_OF_SEALED_CLASS = WarningCode(
+ 'SUBTYPE_OF_SEALED_CLASS',
+ "The class '{0}' shouldn't be extended, mixed in, or implemented because "
+ "it's sealed.",
+ correctionMessage:
+ "Try composing instead of inheriting, or refer to the documentation of "
+ "'{0}' for more information.",
+ hasPublishedDocs: true,
+ );
+
/// Parameters:
/// 0: the unicode sequence of the code point.
static const WarningCode TEXT_DIRECTION_CODE_POINT_IN_COMMENT = WarningCode(
@@ -6678,6 +6941,15 @@
);
/// Parameters:
+ /// 0: the name of the undefined parameter
+ /// 1: the name of the targeted member
+ static const WarningCode UNDEFINED_REFERENCED_PARAMETER = WarningCode(
+ 'UNDEFINED_REFERENCED_PARAMETER',
+ "The parameter '{0}' isn't defined by '{1}'.",
+ hasPublishedDocs: true,
+ );
+
+ /// Parameters:
/// 0: the name of the library being imported
/// 1: the name in the show clause that isn't defined in the library
static const WarningCode UNDEFINED_SHOWN_NAME = WarningCode(
@@ -6700,13 +6972,84 @@
/// This is the new replacement for [HintCode.UNNECESSARY_FINAL].
static const HintCode UNNECESSARY_FINAL = HintCode.UNNECESSARY_FINAL;
- /// This is the new replacement for [HintCode.UNNECESSARY_TYPE_CHECK_FALSE].
- static const HintCode UNNECESSARY_TYPE_CHECK_FALSE =
- HintCode.UNNECESSARY_TYPE_CHECK_FALSE;
+ /// No parameters.
+ static const WarningCode UNNECESSARY_NAN_COMPARISON_FALSE = WarningCode(
+ 'UNNECESSARY_NAN_COMPARISON',
+ "A double can't equal 'double.nan', so the condition is always 'false'.",
+ correctionMessage: "Try using 'double.isNan', or removing the condition.",
+ uniqueName: 'UNNECESSARY_NAN_COMPARISON_FALSE',
+ );
- /// This is the new replacement for [HintCode.UNNECESSARY_TYPE_CHECK_TRUE].
- static const HintCode UNNECESSARY_TYPE_CHECK_TRUE =
- HintCode.UNNECESSARY_TYPE_CHECK_TRUE;
+ /// No parameters.
+ static const WarningCode UNNECESSARY_NAN_COMPARISON_TRUE = WarningCode(
+ 'UNNECESSARY_NAN_COMPARISON',
+ "A double can't equal 'double.nan', so the condition is always 'true'.",
+ correctionMessage: "Try using 'double.isNan', or removing the condition.",
+ uniqueName: 'UNNECESSARY_NAN_COMPARISON_TRUE',
+ );
+
+ /// No parameters.
+ static const WarningCode UNNECESSARY_NO_SUCH_METHOD = WarningCode(
+ 'UNNECESSARY_NO_SUCH_METHOD',
+ "Unnecessary 'noSuchMethod' declaration.",
+ correctionMessage: "Try removing the declaration of 'noSuchMethod'.",
+ hasPublishedDocs: true,
+ );
+
+ /// No parameters.
+ static const WarningCode UNNECESSARY_NULL_COMPARISON_FALSE = WarningCode(
+ 'UNNECESSARY_NULL_COMPARISON',
+ "The operand can't be null, so the condition is always 'false'.",
+ correctionMessage:
+ "Try removing the condition, an enclosing condition, or the whole "
+ "conditional statement.",
+ hasPublishedDocs: true,
+ uniqueName: 'UNNECESSARY_NULL_COMPARISON_FALSE',
+ );
+
+ /// No parameters.
+ static const WarningCode UNNECESSARY_NULL_COMPARISON_TRUE = WarningCode(
+ 'UNNECESSARY_NULL_COMPARISON',
+ "The operand can't be null, so the condition is always 'true'.",
+ correctionMessage: "Remove the condition.",
+ hasPublishedDocs: true,
+ uniqueName: 'UNNECESSARY_NULL_COMPARISON_TRUE',
+ );
+
+ /// Parameters:
+ /// 0: the name of the type
+ static const WarningCode UNNECESSARY_QUESTION_MARK = WarningCode(
+ 'UNNECESSARY_QUESTION_MARK',
+ "The '?' is unnecessary because '{0}' is nullable without it.",
+ hasPublishedDocs: true,
+ );
+
+ /// No parameters.
+ static const WarningCode UNNECESSARY_SET_LITERAL = WarningCode(
+ 'UNNECESSARY_SET_LITERAL',
+ "Braces unnecessarily wrap this expression in a set literal.",
+ correctionMessage: "Try removing the set literal around the expression.",
+ );
+
+ /// No parameters.
+ static const WarningCode UNNECESSARY_TYPE_CHECK_FALSE = WarningCode(
+ 'UNNECESSARY_TYPE_CHECK',
+ "Unnecessary type check; the result is always 'false'.",
+ correctionMessage:
+ "Try correcting the type check, or removing the type check.",
+ hasPublishedDocs: true,
+ uniqueName: 'UNNECESSARY_TYPE_CHECK_FALSE',
+ );
+
+ /// No parameters.
+ static const WarningCode UNNECESSARY_TYPE_CHECK_TRUE = WarningCode(
+ 'UNNECESSARY_TYPE_CHECK',
+ "Unnecessary type check; the result is always 'true'.",
+ correctionMessage:
+ "Try correcting the type check, or removing the type check.",
+ hasPublishedDocs: true,
+ uniqueName: 'UNNECESSARY_TYPE_CHECK_TRUE',
+ );
/// No parameters.
static const WarningCode UNNECESSARY_WILDCARD_PATTERN = WarningCode(
@@ -6715,6 +7058,10 @@
correctionMessage: "Try removing the wildcard pattern.",
);
+ /// This is the new replacement for [HintCode.UNREACHABLE_SWITCH_CASE].
+ static const HintCode UNREACHABLE_SWITCH_CASE =
+ HintCode.UNREACHABLE_SWITCH_CASE;
+
/// Parameters:
/// 0: the name of the exception variable
static const WarningCode UNUSED_CATCH_CLAUSE = WarningCode(
@@ -6747,9 +7094,48 @@
/// This is the new replacement for [HintCode.UNUSED_IMPORT].
static const HintCode UNUSED_IMPORT = HintCode.UNUSED_IMPORT;
+ /// Parameters:
+ /// 0: the label that isn't used
+ static const WarningCode UNUSED_LABEL = WarningCode(
+ 'UNUSED_LABEL',
+ "The label '{0}' isn't used.",
+ correctionMessage:
+ "Try removing the label, or using it in either a 'break' or 'continue' "
+ "statement.",
+ hasPublishedDocs: true,
+ );
+
/// This is the new replacement for [HintCode.UNUSED_LOCAL_VARIABLE].
static const HintCode UNUSED_LOCAL_VARIABLE = HintCode.UNUSED_LOCAL_VARIABLE;
+ /// Parameters:
+ /// 0: the name of the annotated method, property or function
+ static const WarningCode UNUSED_RESULT = WarningCode(
+ 'UNUSED_RESULT',
+ "The value of '{0}' should be used.",
+ correctionMessage:
+ "Try using the result by invoking a member, passing it to a function, "
+ "or returning it from this function.",
+ hasPublishedDocs: true,
+ );
+
+ /// The result of invoking a method, property, or function annotated with
+ /// `@useResult` must be used (assigned, passed to a function as an argument,
+ /// or returned by a function).
+ ///
+ /// Parameters:
+ /// 0: the name of the annotated method, property or function
+ /// 1: message details
+ static const WarningCode UNUSED_RESULT_WITH_MESSAGE = WarningCode(
+ 'UNUSED_RESULT',
+ "'{0}' should be used. {1}.",
+ correctionMessage:
+ "Try using the result by invoking a member, passing it to a function, "
+ "or returning it from this function.",
+ hasPublishedDocs: true,
+ uniqueName: 'UNUSED_RESULT_WITH_MESSAGE',
+ );
+
/// Initialize a newly created error code to have the given [name].
const WarningCode(
String name,
diff --git a/analyzer/lib/src/error/dead_code_verifier.dart b/analyzer/lib/src/error/dead_code_verifier.dart
index 516ab84..70975a7 100644
--- a/analyzer/lib/src/error/dead_code_verifier.dart
+++ b/analyzer/lib/src/error/dead_code_verifier.dart
@@ -102,21 +102,21 @@
Namespace namespace =
NamespaceBuilder().createExportNamespaceForLibrary(library);
NodeList<SimpleIdentifier> names;
- ErrorCode hintCode;
+ ErrorCode warningCode;
if (combinator is HideCombinator) {
names = combinator.hiddenNames;
- hintCode = WarningCode.UNDEFINED_HIDDEN_NAME;
+ warningCode = WarningCode.UNDEFINED_HIDDEN_NAME;
} else {
names = (combinator as ShowCombinator).shownNames;
- hintCode = WarningCode.UNDEFINED_SHOWN_NAME;
+ warningCode = WarningCode.UNDEFINED_SHOWN_NAME;
}
for (SimpleIdentifier name in names) {
String nameStr = name.name;
Element? element = namespace.get(nameStr);
element ??= namespace.get("$nameStr=");
if (element == null) {
- _errorReporter
- .reportErrorForNode(hintCode, name, [library.identifier, nameStr]);
+ _errorReporter.reportErrorForNode(
+ warningCode, name, [library.identifier, nameStr]);
}
}
}
@@ -129,7 +129,7 @@
} finally {
for (Label label in labelTracker.unusedLabels()) {
_errorReporter.reportErrorForNode(
- HintCode.UNUSED_LABEL, label, [label.label.name]);
+ WarningCode.UNUSED_LABEL, label, [label.label.name]);
}
_labelTracker = labelTracker.outerTracker;
}
@@ -168,7 +168,7 @@
var offset = node.operator.offset;
var length = node.rightOperand.end - offset;
_errorReporter.reportErrorForOffset(
- HintCode.DEAD_CODE, offset, length);
+ WarningCode.DEAD_CODE, offset, length);
// Only visit the LHS:
lhsCondition.accept(this);
return;
@@ -217,13 +217,13 @@
if (result.value?.toBoolValue() == true) {
// Report error on "else" block: true ? 1 : !2!
_errorReporter.reportErrorForNode(
- HintCode.DEAD_CODE, node.elseExpression);
+ WarningCode.DEAD_CODE, node.elseExpression);
node.thenExpression.accept(this);
return;
} else {
// Report error on "if" block: false ? !1! : 2
_errorReporter.reportErrorForNode(
- HintCode.DEAD_CODE, node.thenExpression);
+ WarningCode.DEAD_CODE, node.thenExpression);
node.elseExpression.accept(this);
return;
}
@@ -243,14 +243,15 @@
// Report error on else block: if(true) {} else {!}
var elseElement = node.elseElement;
if (elseElement != null) {
- _errorReporter.reportErrorForNode(HintCode.DEAD_CODE, elseElement);
+ _errorReporter.reportErrorForNode(
+ WarningCode.DEAD_CODE, elseElement);
node.thenElement.accept(this);
return;
}
} else {
// Report error on if block: if (false) {!} else {}
_errorReporter.reportErrorForNode(
- HintCode.DEAD_CODE, node.thenElement);
+ WarningCode.DEAD_CODE, node.thenElement);
node.elseElement?.accept(this);
return;
}
@@ -271,14 +272,14 @@
var elseStatement = node.elseStatement;
if (elseStatement != null) {
_errorReporter.reportErrorForNode(
- HintCode.DEAD_CODE, elseStatement);
+ WarningCode.DEAD_CODE, elseStatement);
node.thenStatement.accept(this);
return;
}
} else {
// Report error on if block: if (false) {!} else {}
_errorReporter.reportErrorForNode(
- HintCode.DEAD_CODE, node.thenStatement);
+ WarningCode.DEAD_CODE, node.thenStatement);
node.elseStatement?.accept(this);
return;
}
@@ -336,7 +337,7 @@
if (result != null) {
if (result.value?.toBoolValue() == false) {
// Report error on while block: while (false) {!}
- _errorReporter.reportErrorForNode(HintCode.DEAD_CODE, node.body);
+ _errorReporter.reportErrorForNode(WarningCode.DEAD_CODE, node.body);
return;
}
}
@@ -378,7 +379,8 @@
}
int offset = nextStatement.offset;
int length = lastStatement.end - offset;
- _errorReporter.reportErrorForOffset(HintCode.DEAD_CODE, offset, length);
+ _errorReporter.reportErrorForOffset(
+ WarningCode.DEAD_CODE, offset, length);
return;
}
}
@@ -489,7 +491,7 @@
}
if (node is SwitchMember && node == firstDeadNode) {
- _errorReporter.reportErrorForToken(HintCode.DEAD_CODE, node.keyword);
+ _errorReporter.reportErrorForToken(WarningCode.DEAD_CODE, node.keyword);
_firstDeadNode = null;
return;
}
@@ -537,9 +539,9 @@
whileOffset = body.rightBracket.offset;
}
_errorReporter.reportErrorForOffset(
- HintCode.DEAD_CODE, doOffset, doEnd - doOffset);
+ WarningCode.DEAD_CODE, doOffset, doEnd - doOffset);
_errorReporter.reportErrorForOffset(
- HintCode.DEAD_CODE, whileOffset, whileEnd - whileOffset);
+ WarningCode.DEAD_CODE, whileOffset, whileEnd - whileOffset);
offset = parent.semicolon.next!.offset;
if (parent.hasBreakStatement) {
offset = node.end;
@@ -560,7 +562,7 @@
var length = node.end - offset;
if (length > 0) {
_errorReporter.reportErrorForOffset(
- HintCode.DEAD_CODE, offset, length);
+ WarningCode.DEAD_CODE, offset, length);
}
}
@@ -664,7 +666,7 @@
var beginToken = updaters.beginToken;
var endToken = updaters.endToken;
if (beginToken != null && endToken != null) {
- _errorReporter.reportErrorForOffset(HintCode.DEAD_CODE,
+ _errorReporter.reportErrorForOffset(WarningCode.DEAD_CODE,
beginToken.offset, endToken.end - beginToken.offset);
}
}
@@ -697,7 +699,7 @@
node = parent!;
parent = node.parent;
}
- _errorReporter.reportErrorForNode(HintCode.DEAD_CODE, node);
+ _errorReporter.reportErrorForNode(WarningCode.DEAD_CODE, node);
}
}
}
@@ -743,7 +745,7 @@
_errorReporter(
catchClauses[index + 1],
catchClauses.last,
- HintCode.DEAD_CODE_CATCH_FOLLOWING_CATCH,
+ WarningCode.DEAD_CODE_CATCH_FOLLOWING_CATCH,
const [],
);
_done = true;
@@ -758,7 +760,7 @@
_errorReporter(
catchClause,
catchClauses.last,
- HintCode.DEAD_CODE_ON_CATCH_SUBTYPE,
+ WarningCode.DEAD_CODE_ON_CATCH_SUBTYPE,
[currentType, type],
);
_done = true;
diff --git a/analyzer/lib/src/error/error_code_values.g.dart b/analyzer/lib/src/error/error_code_values.g.dart
index 848ef24..1266984 100644
--- a/analyzer/lib/src/error/error_code_values.g.dart
+++ b/analyzer/lib/src/error/error_code_values.g.dart
@@ -235,7 +235,6 @@
CompileTimeErrorCode.INCONSISTENT_INHERITANCE_GETTER_AND_METHOD,
CompileTimeErrorCode.INCONSISTENT_LANGUAGE_VERSION_OVERRIDE,
CompileTimeErrorCode.INCONSISTENT_PATTERN_VARIABLE_LOGICAL_OR,
- CompileTimeErrorCode.INCONSISTENT_PATTERN_VARIABLE_SHARED_CASE_SCOPE,
CompileTimeErrorCode.INITIALIZER_FOR_NON_EXISTENT_FIELD,
CompileTimeErrorCode.INITIALIZER_FOR_STATIC_FIELD,
CompileTimeErrorCode.INITIALIZING_FORMAL_FOR_NON_EXISTENT_FIELD,
@@ -315,6 +314,7 @@
CompileTimeErrorCode.MIXIN_APPLICATION_NOT_IMPLEMENTED_INTERFACE,
CompileTimeErrorCode.MIXIN_APPLICATION_NO_CONCRETE_SUPER_INVOKED_MEMBER,
CompileTimeErrorCode.MIXIN_APPLICATION_NO_CONCRETE_SUPER_INVOKED_SETTER,
+ CompileTimeErrorCode.MIXIN_CLASS_DECLARATION_EXTENDS_NOT_OBJECT,
CompileTimeErrorCode.MIXIN_CLASS_DECLARES_CONSTRUCTOR,
CompileTimeErrorCode.MIXIN_DEFERRED_CLASS,
CompileTimeErrorCode.MIXIN_INHERITS_FROM_NOT_OBJECT,
@@ -402,6 +402,10 @@
CompileTimeErrorCode.PATTERN_TYPE_MISMATCH_IN_IRREFUTABLE_CONTEXT,
CompileTimeErrorCode.PATTERN_VARIABLE_ASSIGNMENT_INSIDE_GUARD,
CompileTimeErrorCode
+ .PATTERN_VARIABLE_SHARED_CASE_SCOPE_DIFFERENT_FINALITY_OR_TYPE,
+ CompileTimeErrorCode.PATTERN_VARIABLE_SHARED_CASE_SCOPE_HAS_LABEL,
+ CompileTimeErrorCode.PATTERN_VARIABLE_SHARED_CASE_SCOPE_NOT_ALL_CASES,
+ CompileTimeErrorCode
.POSITIONAL_SUPER_FORMAL_PARAMETER_WITH_POSITIONAL_ARGUMENT,
CompileTimeErrorCode.PREFIX_COLLIDES_WITH_TOP_LEVEL_MEMBER,
CompileTimeErrorCode.PREFIX_IDENTIFIER_NOT_FOLLOWED_BY_DOT,
@@ -429,6 +433,7 @@
CompileTimeErrorCode.REDIRECT_TO_TYPE_ALIAS_EXPANDS_TO_TYPE_PARAMETER,
CompileTimeErrorCode.REFERENCED_BEFORE_DECLARATION,
CompileTimeErrorCode.REFUTABLE_PATTERN_IN_IRREFUTABLE_CONTEXT,
+ CompileTimeErrorCode.RELATIONAL_PATTERN_OPERAND_TYPE_NOT_ASSIGNABLE,
CompileTimeErrorCode
.RELATIONAL_PATTERN_OPERATOR_RETURN_TYPE_NOT_ASSIGNABLE_TO_BOOL,
CompileTimeErrorCode.REST_ELEMENT_NOT_LAST_IN_MAP_PATTERN,
@@ -448,6 +453,8 @@
CompileTimeErrorCode.SHARED_DEFERRED_PREFIX,
CompileTimeErrorCode.SPREAD_EXPRESSION_FROM_DEFERRED_LIBRARY,
CompileTimeErrorCode.STATIC_ACCESS_TO_INSTANCE_MEMBER,
+ CompileTimeErrorCode.SUBTYPE_OF_BASE_IS_NOT_BASE_FINAL_OR_SEALED,
+ CompileTimeErrorCode.SUBTYPE_OF_FINAL_IS_NOT_BASE_FINAL_OR_SEALED,
CompileTimeErrorCode.SUPER_FORMAL_PARAMETER_TYPE_IS_NOT_SUBTYPE_OF_ASSOCIATED,
CompileTimeErrorCode.SUPER_FORMAL_PARAMETER_WITHOUT_ASSOCIATED_NAMED,
CompileTimeErrorCode.SUPER_FORMAL_PARAMETER_WITHOUT_ASSOCIATED_POSITIONAL,
@@ -576,11 +583,7 @@
FfiCode.SUBTYPE_OF_STRUCT_CLASS_IN_EXTENDS,
FfiCode.SUBTYPE_OF_STRUCT_CLASS_IN_IMPLEMENTS,
FfiCode.SUBTYPE_OF_STRUCT_CLASS_IN_WITH,
- HintCode.ASSIGNMENT_OF_DO_NOT_STORE,
HintCode.CAN_BE_NULL_AFTER_NULL_AWARE,
- HintCode.DEAD_CODE,
- HintCode.DEAD_CODE_CATCH_FOLLOWING_CATCH,
- HintCode.DEAD_CODE_ON_CATCH_SUBTYPE,
HintCode.DEPRECATED_COLON_FOR_DEFAULT_VALUE,
HintCode.DEPRECATED_EXPORT_USE,
HintCode.DEPRECATED_MEMBER_USE,
@@ -588,44 +591,19 @@
HintCode.DEPRECATED_MEMBER_USE_FROM_SAME_PACKAGE_WITH_MESSAGE,
HintCode.DEPRECATED_MEMBER_USE_WITH_MESSAGE,
HintCode.DIVISION_OPTIMIZATION,
- HintCode.FILE_IMPORT_INSIDE_LIB_REFERENCES_FILE_OUTSIDE,
- HintCode.FILE_IMPORT_OUTSIDE_LIB_REFERENCES_FILE_INSIDE,
HintCode.IMPORT_DEFERRED_LIBRARY_WITH_LOAD_FUNCTION,
HintCode.IMPORT_OF_LEGACY_LIBRARY_INTO_NULL_SAFE,
- HintCode.MIXIN_ON_SEALED_CLASS,
- HintCode.NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR,
- HintCode.NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR_USING_NEW,
- HintCode.NULL_CHECK_ALWAYS_FAILS,
- HintCode.OVERRIDE_ON_NON_OVERRIDING_FIELD,
- HintCode.OVERRIDE_ON_NON_OVERRIDING_GETTER,
- HintCode.OVERRIDE_ON_NON_OVERRIDING_METHOD,
- HintCode.OVERRIDE_ON_NON_OVERRIDING_SETTER,
- HintCode.STRICT_RAW_TYPE,
- HintCode.SUBTYPE_OF_SEALED_CLASS,
- HintCode.UNDEFINED_REFERENCED_PARAMETER,
HintCode.UNIGNORABLE_IGNORE,
HintCode.UNNECESSARY_CAST,
HintCode.UNNECESSARY_FINAL,
HintCode.UNNECESSARY_IGNORE,
HintCode.UNNECESSARY_IMPORT,
- HintCode.UNNECESSARY_NAN_COMPARISON_FALSE,
- HintCode.UNNECESSARY_NAN_COMPARISON_TRUE,
- HintCode.UNNECESSARY_NO_SUCH_METHOD,
- HintCode.UNNECESSARY_NULL_COMPARISON_FALSE,
- HintCode.UNNECESSARY_NULL_COMPARISON_TRUE,
- HintCode.UNNECESSARY_QUESTION_MARK,
- HintCode.UNNECESSARY_SET_LITERAL,
- HintCode.UNNECESSARY_TYPE_CHECK_FALSE,
- HintCode.UNNECESSARY_TYPE_CHECK_TRUE,
HintCode.UNREACHABLE_SWITCH_CASE,
HintCode.UNUSED_ELEMENT,
HintCode.UNUSED_ELEMENT_PARAMETER,
HintCode.UNUSED_FIELD,
HintCode.UNUSED_IMPORT,
- HintCode.UNUSED_LABEL,
HintCode.UNUSED_LOCAL_VARIABLE,
- HintCode.UNUSED_RESULT,
- HintCode.UNUSED_RESULT_WITH_MESSAGE,
HintCode.UNUSED_SHOWN_NAME,
LanguageCode.IMPLICIT_DYNAMIC_FIELD,
LanguageCode.IMPLICIT_DYNAMIC_FUNCTION,
@@ -741,6 +719,7 @@
ParserErrorCode.FINAL_CONSTRUCTOR,
ParserErrorCode.FINAL_ENUM,
ParserErrorCode.FINAL_METHOD,
+ ParserErrorCode.FINAL_MIXIN_CLASS,
ParserErrorCode.FINAL_TYPEDEF,
ParserErrorCode.FUNCTION_TYPED_PARAMETER_VAR,
ParserErrorCode.GETTER_CONSTRUCTOR,
@@ -752,6 +731,7 @@
ParserErrorCode.IMPLEMENTS_BEFORE_WITH,
ParserErrorCode.IMPORT_DIRECTIVE_AFTER_PART_DIRECTIVE,
ParserErrorCode.INITIALIZED_VARIABLE_IN_FOR_EACH,
+ ParserErrorCode.INTERFACE_MIXIN_CLASS,
ParserErrorCode.INVALID_AWAIT_IN_FOR,
ParserErrorCode.INVALID_CODE_POINT,
ParserErrorCode.INVALID_COMMENT_REFERENCE,
@@ -845,6 +825,7 @@
ParserErrorCode.RECORD_TYPE_ONE_POSITIONAL_NO_TRAILING_COMMA,
ParserErrorCode.REDIRECTING_CONSTRUCTOR_WITH_BODY,
ParserErrorCode.REDIRECTION_IN_NON_FACTORY_CONSTRUCTOR,
+ ParserErrorCode.SEALED_MIXIN_CLASS,
ParserErrorCode.SETTER_CONSTRUCTOR,
ParserErrorCode.SETTER_IN_FUNCTION,
ParserErrorCode.STACK_OVERFLOW,
@@ -911,11 +892,15 @@
TodoCode.TODO,
TodoCode.UNDONE,
WarningCode.ARGUMENT_TYPE_NOT_ASSIGNABLE_TO_ERROR_HANDLER,
+ WarningCode.ASSIGNMENT_OF_DO_NOT_STORE,
WarningCode.BODY_MIGHT_COMPLETE_NORMALLY_CATCH_ERROR,
WarningCode.BODY_MIGHT_COMPLETE_NORMALLY_NULLABLE,
WarningCode.CAST_FROM_NULLABLE_ALWAYS_FAILS,
WarningCode.CAST_FROM_NULL_ALWAYS_FAILS,
WarningCode.CONSTANT_PATTERN_NEVER_MATCHES_VALUE_TYPE,
+ WarningCode.DEAD_CODE,
+ WarningCode.DEAD_CODE_CATCH_FOLLOWING_CATCH,
+ WarningCode.DEAD_CODE_ON_CATCH_SUBTYPE,
WarningCode.DEPRECATED_EXTENDS_FUNCTION,
WarningCode.DEPRECATED_IMPLEMENTS_FUNCTION,
WarningCode.DEPRECATED_MIXIN_FUNCTION,
@@ -971,14 +956,23 @@
WarningCode.MISSING_REQUIRED_PARAM,
WarningCode.MISSING_REQUIRED_PARAM_WITH_DETAILS,
WarningCode.MISSING_RETURN,
+ WarningCode.MIXIN_ON_SEALED_CLASS,
WarningCode.MUST_BE_IMMUTABLE,
WarningCode.MUST_CALL_SUPER,
+ WarningCode.NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR,
+ WarningCode.NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR_USING_NEW,
WarningCode.NULLABLE_TYPE_IN_CATCH_CLAUSE,
WarningCode.NULL_ARGUMENT_TO_NON_NULL_TYPE,
WarningCode.NULL_AWARE_BEFORE_OPERATOR,
WarningCode.NULL_AWARE_IN_CONDITION,
WarningCode.NULL_AWARE_IN_LOGICAL_OPERATOR,
+ WarningCode.NULL_CHECK_ALWAYS_FAILS,
+ WarningCode.OVERRIDE_ON_NON_OVERRIDING_FIELD,
+ WarningCode.OVERRIDE_ON_NON_OVERRIDING_GETTER,
+ WarningCode.OVERRIDE_ON_NON_OVERRIDING_METHOD,
+ WarningCode.OVERRIDE_ON_NON_OVERRIDING_SETTER,
WarningCode.RECEIVER_OF_TYPE_NEVER,
+ WarningCode.RECORD_LITERAL_ONE_POSITIONAL_NO_TRAILING_COMMA,
WarningCode.REMOVED_LINT_USE,
WarningCode.REPLACED_LINT_USE,
WarningCode.RETURN_OF_DO_NOT_STORE,
@@ -994,16 +988,32 @@
WarningCode.SDK_VERSION_IS_EXPRESSION_IN_CONST_CONTEXT,
WarningCode.SDK_VERSION_NEVER,
WarningCode.SDK_VERSION_SET_LITERAL,
+ WarningCode.SDK_VERSION_SINCE,
WarningCode.SDK_VERSION_UI_AS_CODE,
WarningCode.SDK_VERSION_UI_AS_CODE_IN_CONST_CONTEXT,
+ WarningCode.STRICT_RAW_TYPE,
+ WarningCode.SUBTYPE_OF_SEALED_CLASS,
WarningCode.TEXT_DIRECTION_CODE_POINT_IN_COMMENT,
WarningCode.TEXT_DIRECTION_CODE_POINT_IN_LITERAL,
WarningCode.TYPE_CHECK_IS_NOT_NULL,
WarningCode.TYPE_CHECK_IS_NULL,
WarningCode.UNDEFINED_HIDDEN_NAME,
+ WarningCode.UNDEFINED_REFERENCED_PARAMETER,
WarningCode.UNDEFINED_SHOWN_NAME,
WarningCode.UNNECESSARY_CAST_PATTERN,
+ WarningCode.UNNECESSARY_NAN_COMPARISON_FALSE,
+ WarningCode.UNNECESSARY_NAN_COMPARISON_TRUE,
+ WarningCode.UNNECESSARY_NO_SUCH_METHOD,
+ WarningCode.UNNECESSARY_NULL_COMPARISON_FALSE,
+ WarningCode.UNNECESSARY_NULL_COMPARISON_TRUE,
+ WarningCode.UNNECESSARY_QUESTION_MARK,
+ WarningCode.UNNECESSARY_SET_LITERAL,
+ WarningCode.UNNECESSARY_TYPE_CHECK_FALSE,
+ WarningCode.UNNECESSARY_TYPE_CHECK_TRUE,
WarningCode.UNNECESSARY_WILDCARD_PATTERN,
WarningCode.UNUSED_CATCH_CLAUSE,
WarningCode.UNUSED_CATCH_STACK,
+ WarningCode.UNUSED_LABEL,
+ WarningCode.UNUSED_RESULT,
+ WarningCode.UNUSED_RESULT_WITH_MESSAGE,
];
diff --git a/analyzer/lib/src/error/imports_verifier.dart b/analyzer/lib/src/error/imports_verifier.dart
index baca24c..9db227a 100644
--- a/analyzer/lib/src/error/imports_verifier.dart
+++ b/analyzer/lib/src/error/imports_verifier.dart
@@ -189,7 +189,7 @@
/// otherwise a [HintCode.UNUSED_IMPORT] hint is generated with
/// [generateUnusedImportHints].
///
-/// Additionally, [generateDuplicateImportHints] generates
+/// Additionally, [generateDuplicateImportWarnings] generates
/// [HintCode.DUPLICATE_IMPORT] hints and [HintCode.UNUSED_SHOWN_NAME] hints.
///
/// While this class does not yet have support for an "Organize Imports" action,
@@ -316,9 +316,9 @@
/// Any time after the defining compilation unit has been visited by this
/// visitor, this method can be called to report an
- /// [StaticWarningCode.DUPLICATE_EXPORT] hint for each of the export
+ /// [WarningCode.DUPLICATE_EXPORT] hint for each of the export
/// directives in the [_duplicateExports] list.
- void generateDuplicateExportHints(ErrorReporter errorReporter) {
+ void generateDuplicateExportWarnings(ErrorReporter errorReporter) {
var length = _duplicateExports.length;
for (var i = 0; i < length; i++) {
errorReporter.reportErrorForNode(
@@ -328,9 +328,9 @@
/// Any time after the defining compilation unit has been visited by this
/// visitor, this method can be called to report an
- /// [StaticWarningCode.DUPLICATE_IMPORT] hint for each of the import
+ /// [WarningCode.DUPLICATE_IMPORT] hint for each of the import
/// directives in the [_duplicateImports] list.
- void generateDuplicateImportHints(ErrorReporter errorReporter) {
+ void generateDuplicateImportWarnings(ErrorReporter errorReporter) {
var length = _duplicateImports.length;
for (var i = 0; i < length; i++) {
errorReporter.reportErrorForNode(
@@ -338,13 +338,13 @@
}
}
- /// Report a [StaticWarningCode.DUPLICATE_SHOWN_NAME] and
- /// [StaticWarningCode.DUPLICATE_HIDDEN_NAME] hints for each duplicate shown
- /// or hidden name.
+ /// Report a [WarningCode.DUPLICATE_SHOWN_NAME] and
+ /// [WarningCode.DUPLICATE_HIDDEN_NAME] hints for each duplicate shown or
+ /// hidden name.
///
/// Only call this method after all of the compilation units have been visited
/// by this visitor.
- void generateDuplicateShownHiddenNameHints(ErrorReporter reporter) {
+ void generateDuplicateShownHiddenNameWarnings(ErrorReporter reporter) {
_duplicateHiddenNamesMap.forEach(
(NamespaceDirective directive, List<SimpleIdentifier> identifiers) {
int length = identifiers.length;
diff --git a/analyzer/lib/src/error/literal_element_verifier.dart b/analyzer/lib/src/error/literal_element_verifier.dart
index 4cb6aef..275d9fc 100644
--- a/analyzer/lib/src/error/literal_element_verifier.dart
+++ b/analyzer/lib/src/error/literal_element_verifier.dart
@@ -68,7 +68,7 @@
void _verifyElement(CollectionElement? element) {
if (element is Expression) {
if (forList || forSet) {
- if (!elementType!.isVoid &&
+ if (elementType is! VoidType &&
_errorVerifier.checkForUseOfVoidResult(element)) {
return;
}
@@ -103,14 +103,14 @@
/// Verify that the [entry]'s key and value are assignable to [mapKeyType]
/// and [mapValueType].
void _verifyMapLiteralEntry(MapLiteralEntry entry) {
- var mapKeyType = this.mapKeyType;
- if (!mapKeyType!.isVoid &&
+ var mapKeyType = this.mapKeyType!;
+ if (mapKeyType is! VoidType &&
_errorVerifier.checkForUseOfVoidResult(entry.key)) {
return;
}
- var mapValueType = this.mapValueType;
- if (!mapValueType!.isVoid &&
+ var mapValueType = this.mapValueType!;
+ if (mapValueType is! VoidType &&
_errorVerifier.checkForUseOfVoidResult(entry.value)) {
return;
}
diff --git a/analyzer/lib/src/error/override_verifier.dart b/analyzer/lib/src/error/override_verifier.dart
index dc2004a..59cfd9b 100644
--- a/analyzer/lib/src/error/override_verifier.dart
+++ b/analyzer/lib/src/error/override_verifier.dart
@@ -55,7 +55,7 @@
if (setter != null && _isOverride(setter)) continue;
_errorReporter.reportErrorForToken(
- HintCode.OVERRIDE_ON_NON_OVERRIDING_FIELD,
+ WarningCode.OVERRIDE_ON_NON_OVERRIDING_FIELD,
field.name,
);
}
@@ -68,18 +68,18 @@
if (element.hasOverride && !_isOverride(element)) {
if (element is MethodElement) {
_errorReporter.reportErrorForToken(
- HintCode.OVERRIDE_ON_NON_OVERRIDING_METHOD,
+ WarningCode.OVERRIDE_ON_NON_OVERRIDING_METHOD,
node.name,
);
} else if (element is PropertyAccessorElement) {
if (element.isGetter) {
_errorReporter.reportErrorForToken(
- HintCode.OVERRIDE_ON_NON_OVERRIDING_GETTER,
+ WarningCode.OVERRIDE_ON_NON_OVERRIDING_GETTER,
node.name,
);
} else {
_errorReporter.reportErrorForToken(
- HintCode.OVERRIDE_ON_NON_OVERRIDING_SETTER,
+ WarningCode.OVERRIDE_ON_NON_OVERRIDING_SETTER,
node.name,
);
}
diff --git a/analyzer/lib/src/error/return_type_verifier.dart b/analyzer/lib/src/error/return_type_verifier.dart
index f28c22f..b1d4c40 100644
--- a/analyzer/lib/src/error/return_type_verifier.dart
+++ b/analyzer/lib/src/error/return_type_verifier.dart
@@ -41,7 +41,7 @@
void verifyExpressionFunctionBody(ExpressionFunctionBody node) {
// This enables concise declarations of void functions.
- if (_flattenedReturnType.isVoid) {
+ if (_flattenedReturnType is VoidType) {
return;
}
@@ -91,7 +91,7 @@
// It is a compile-time error if the declared return type of
// a function marked `sync*` or `async*` is `void`.
if (enclosingExecutable.isGenerator) {
- if (enclosingExecutable.returnType.isVoid) {
+ if (enclosingExecutable.returnType is VoidType) {
return reportError();
}
}
@@ -202,7 +202,7 @@
if (enclosingExecutable.isSynchronous) {
// It is a compile-time error if `T` is `void`,
// and `S` is neither `void`, `dynamic`, nor `Null`.
- if (T.isVoid) {
+ if (T is VoidType) {
if (!_isVoidDynamicOrNull(S)) {
reportTypeError();
return;
@@ -210,7 +210,7 @@
}
// It is a compile-time error if `S` is `void`,
// and `T` is neither `void`, `dynamic`, nor `Null`.
- if (S.isVoid) {
+ if (S is VoidType) {
if (!_isVoidDynamicOrNull(T)) {
reportTypeError();
return;
@@ -218,7 +218,7 @@
}
// It is a compile-time error if `S` is not `void`,
// and `S` is not assignable to `T`.
- if (!S.isVoid) {
+ if (S is! VoidType) {
if (!_typeSystem.isAssignableTo(S, T)) {
reportTypeError();
return;
@@ -238,7 +238,7 @@
// implementing it now would be a breaking change. So, the code below
// intentionally does not implement the specification.
// https://github.com/dart-lang/sdk/issues/41803#issuecomment-635852474
- if (T.isVoid) {
+ if (T is VoidType) {
if (!_isVoidDynamicOrNull(flatten_S)) {
reportTypeError();
return;
@@ -246,7 +246,7 @@
}
// It is a compile-time error if `flatten(S)` is `void`,
// and `flatten(T)` is neither `void`, `dynamic`, nor `Null`.
- if (flatten_S.isVoid) {
+ if (flatten_S is VoidType) {
if (!_isVoidDynamicOrNull(flatten_T)) {
reportTypeError();
return;
@@ -254,7 +254,7 @@
}
// It is a compile-time error if `flatten(S)` is not `void`,
// and `Future<flatten(S)>` is not assignable to `T`.
- if (!flatten_S.isVoid) {
+ if (flatten_S is! VoidType) {
var future_flatten_S = _typeProvider.futureType(flatten_S);
if (!_typeSystem.isAssignableTo(future_flatten_S, T)) {
reportTypeError();
@@ -321,7 +321,7 @@
if (enclosingExecutable.isSynchronous) {
// It is a compile-time error if `T` is `void`,
// and `S` is neither `void`, `dynamic`, nor `Null`.
- if (T.isVoid) {
+ if (T is VoidType) {
if (!_isVoidDynamicOrNull(S)) {
reportTypeError();
return;
@@ -329,7 +329,7 @@
}
// It is a compile-time error if `S` is `void`,
// and `T` is neither `void` nor `dynamic`.
- if (S.isVoid) {
+ if (S is VoidType) {
if (!_isVoidDynamic(T)) {
reportTypeError();
return;
@@ -337,7 +337,21 @@
}
// It is a compile-time error if `S` is not `void`,
// and `S` is not assignable to `T`.
- if (!S.isVoid) {
+ if (S is! VoidType) {
+ if (T is RecordType) {
+ if (S is! RecordType && T.positionalFields.length == 1) {
+ var field = T.positionalFields.first;
+ if (_typeSystem.isAssignableTo(field.type, S)) {
+ _errorReporter.reportErrorForNode(
+ WarningCode.RECORD_LITERAL_ONE_POSITIONAL_NO_TRAILING_COMMA,
+ expression,
+ [],
+ );
+ return;
+ }
+ }
+ }
+
if (!_typeSystem.isAssignableTo(S, T)) {
reportTypeError();
return;
@@ -352,7 +366,7 @@
var flatten_S = _typeSystem.flatten(S);
// It is a compile-time error if `flatten(T)` is `void`,
// and `flatten(S)` is neither `void`, `dynamic`, nor `Null`.
- if (T_v.isVoid) {
+ if (T_v is VoidType) {
if (!_isVoidDynamicOrNull(flatten_S)) {
reportTypeError();
return;
@@ -360,7 +374,7 @@
}
// It is a compile-time error if `flatten(S)` is `void`,
// and `flatten(T)` is neither `void`, `dynamic`.
- if (flatten_S.isVoid) {
+ if (flatten_S is VoidType) {
if (!_isVoidDynamic(T_v)) {
reportTypeError();
return;
@@ -368,7 +382,7 @@
}
// It is a compile-time error if `flatten(S)` is not `void`,
// and `Future<flatten(S)>` is not assignable to `T`.
- if (!flatten_S.isVoid) {
+ if (flatten_S is! VoidType) {
if (!_typeSystem.isAssignableTo(S, T_v) &&
!_typeSystem.isSubtypeOf(flatten_S, T_v)) {
reportTypeError();
@@ -434,10 +448,10 @@
}
static bool _isVoidDynamic(DartType type) {
- return type.isVoid || type.isDynamic;
+ return type is VoidType || type.isDynamic;
}
static bool _isVoidDynamicOrNull(DartType type) {
- return type.isVoid || type.isDynamic || type.isDartCoreNull;
+ return type is VoidType || type.isDynamic || type.isDartCoreNull;
}
}
diff --git a/analyzer/lib/src/error/type_arguments_verifier.dart b/analyzer/lib/src/error/type_arguments_verifier.dart
index ce1acff..3a1f861 100644
--- a/analyzer/lib/src/error/type_arguments_verifier.dart
+++ b/analyzer/lib/src/error/type_arguments_verifier.dart
@@ -308,7 +308,7 @@
// See https://github.com/dart-lang/language/blob/master/resources/type-system/strict-raw-types.md#conditions-for-a-raw-type-hint
} else {
_errorReporter
- .reportErrorForNode(HintCode.STRICT_RAW_TYPE, node, [type]);
+ .reportErrorForNode(WarningCode.STRICT_RAW_TYPE, node, [type]);
}
}
}
diff --git a/analyzer/lib/src/error/unused_local_elements_verifier.dart b/analyzer/lib/src/error/unused_local_elements_verifier.dart
index dfc767e..39470b3 100644
--- a/analyzer/lib/src/error/unused_local_elements_verifier.dart
+++ b/analyzer/lib/src/error/unused_local_elements_verifier.dart
@@ -11,6 +11,7 @@
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/error/listener.dart';
+import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/ast/extensions.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
@@ -499,9 +500,13 @@
}
@override
- void visitDeclaredVariablePattern(DeclaredVariablePattern node) {
+ void visitDeclaredVariablePattern(
+ covariant DeclaredVariablePatternImpl node,
+ ) {
final declaredElement = node.declaredElement!;
- _visitLocalVariableElement(declaredElement);
+ if (!declaredElement.isDuplicate) {
+ _visitLocalVariableElement(declaredElement);
+ }
super.visitDeclaredVariablePattern(node);
}
diff --git a/analyzer/lib/src/error/use_result_verifier.dart b/analyzer/lib/src/error/use_result_verifier.dart
index 17be259..fb02494 100644
--- a/analyzer/lib/src/error/use_result_verifier.dart
+++ b/analyzer/lib/src/error/use_result_verifier.dart
@@ -7,7 +7,7 @@
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
-import 'package:analyzer/src/dart/error/hint_codes.dart';
+import 'package:analyzer/src/error/codes.g.dart';
import 'package:collection/collection.dart';
class UseResultVerifier {
@@ -101,9 +101,9 @@
var message = _getUseResultMessage(annotation);
if (message == null || message.isEmpty) {
_errorReporter.reportErrorForNode(
- HintCode.UNUSED_RESULT, toAnnotate, [displayName]);
+ WarningCode.UNUSED_RESULT, toAnnotate, [displayName]);
} else {
- _errorReporter.reportErrorForNode(HintCode.UNUSED_RESULT_WITH_MESSAGE,
+ _errorReporter.reportErrorForNode(WarningCode.UNUSED_RESULT_WITH_MESSAGE,
toAnnotate, [displayName, message]);
}
}
diff --git a/analyzer/lib/src/fasta/ast_builder.dart b/analyzer/lib/src/fasta/ast_builder.dart
index f6b47d9..7137edd 100644
--- a/analyzer/lib/src/fasta/ast_builder.dart
+++ b/analyzer/lib/src/fasta/ast_builder.dart
@@ -4755,7 +4755,13 @@
var typeParameters = pop() as TypeParameterListImpl?;
var name = pop() as SimpleIdentifierImpl;
var metadata = pop() as List<AnnotationImpl>?;
- var comment = _findComment(metadata, mixinKeyword);
+
+ final begin = sealedKeyword ??
+ baseKeyword ??
+ interfaceKeyword ??
+ finalKeyword ??
+ mixinKeyword;
+ var comment = _findComment(metadata, begin);
_classLikeBuilder = _MixinDeclarationBuilder(
comment: comment,
@@ -5478,7 +5484,8 @@
}
@override
- void handleValuedFormalParameter(Token equals, Token token) {
+ void handleValuedFormalParameter(
+ Token equals, Token token, FormalParameterKind kind) {
assert(optional('=', equals) || optional(':', equals));
debugEvent("ValuedFormalParameter");
diff --git a/analyzer/lib/src/generated/error_detection_helpers.dart b/analyzer/lib/src/generated/error_detection_helpers.dart
index e3f078a..2b8d774 100644
--- a/analyzer/lib/src/generated/error_detection_helpers.dart
+++ b/analyzer/lib/src/generated/error_detection_helpers.dart
@@ -34,7 +34,8 @@
DartType actualStaticType,
ErrorCode errorCode,
{Map<DartType, NonPromotionReason> Function()? whyNotPromoted}) {
- if (!expectedStaticType.isVoid && checkForUseOfVoidResult(expression)) {
+ if (expectedStaticType is! VoidType &&
+ checkForUseOfVoidResult(expression)) {
return;
}
@@ -67,7 +68,8 @@
DartType expectedStaticType,
ErrorCode errorCode,
{Map<DartType, NonPromotionReason> Function()? whyNotPromoted}) {
- if (!expectedStaticType.isVoid && checkForUseOfVoidResult(expression)) {
+ if (expectedStaticType is! VoidType &&
+ checkForUseOfVoidResult(expression)) {
return;
}
@@ -82,6 +84,21 @@
return node;
}
+ if (expectedStaticType is RecordType) {
+ if (actualStaticType is! RecordType &&
+ expectedStaticType.positionalFields.length == 1) {
+ var field = expectedStaticType.positionalFields.first;
+ if (typeSystem.isAssignableTo(field.type, actualStaticType)) {
+ errorReporter.reportErrorForNode(
+ WarningCode.RECORD_LITERAL_ONE_POSITIONAL_NO_TRAILING_COMMA,
+ expression,
+ [],
+ );
+ return;
+ }
+ }
+ }
+
errorReporter.reportErrorForNode(
errorCode,
getErrorNode(expression),
@@ -108,7 +125,7 @@
// test the static type of the expression
DartType staticType = expression.typeOrThrow;
if (typeSystem.isAssignableTo(staticType, fieldType)) {
- if (!fieldType.isVoid) {
+ if (fieldType is! VoidType) {
checkForUseOfVoidResult(expression);
}
return;
diff --git a/analyzer/lib/src/generated/error_verifier.dart b/analyzer/lib/src/generated/error_verifier.dart