remove .pub-cache

This commit is contained in:
Álvaro González 2025-09-08 21:18:02 +02:00
parent 666fa20e7b
commit e98b668639
4378 changed files with 1 additions and 1464969 deletions

1
.gitignore vendored
View file

@ -15,3 +15,4 @@
/d4rt-formulas.iml
/build/
/d4rt_formulas.iml
.aider*

View file

@ -1,13 +0,0 @@
Pub Package Cache
=================
This folder is used by Pub to store cached packages used in Dart / Flutter
projects.
The contents of this folder should only be modified using the `dart pub` and
`flutter pub` commands.
Modifying this folder manually can lead to inconsistent behavior.
For details on how manage the `PUB_CACHE`, see:
https://dart.dev/go/pub-cache

View file

@ -1 +0,0 @@
da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f

View file

@ -1 +0,0 @@
974859dc0ff5f37bc4313244b3218c791810d03ab3470a579580279ba971a48d

View file

@ -1 +0,0 @@
d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04

View file

@ -1 +0,0 @@
758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb

View file

@ -1 +0,0 @@
8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea

View file

@ -1 +0,0 @@
ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec

View file

@ -1 +0,0 @@
2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76

View file

@ -1 +0,0 @@
b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68

View file

@ -1 +0,0 @@
5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d

View file

@ -1 +0,0 @@
1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855

View file

@ -1 +0,0 @@
4220081caf1cea231e127a8fd2801b4b55464a51f840b56bb079ce2b3792e9e6

View file

@ -1 +0,0 @@
a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4

View file

@ -1 +0,0 @@
f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694

View file

@ -1 +0,0 @@
c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de

View file

@ -1 +0,0 @@
aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8

View file

@ -1 +0,0 @@
178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571

View file

@ -1 +0,0 @@
dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b

View file

@ -1 +0,0 @@
53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc

View file

@ -1 +0,0 @@
c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7

View file

@ -1 +0,0 @@
c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61

View file

@ -1 +0,0 @@
dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2

View file

@ -1 +0,0 @@
23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394

View file

@ -1 +0,0 @@
41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6

View file

@ -1 +0,0 @@
6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db

View file

@ -1 +0,0 @@
f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc

View file

@ -1 +0,0 @@
75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5

View file

@ -1 +0,0 @@
20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a

View file

@ -1 +0,0 @@
5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585

View file

@ -1 +0,0 @@
e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12

View file

@ -1 +0,0 @@
89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e

View file

@ -1 +0,0 @@
c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3

View file

@ -1 +0,0 @@
3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925

View file

@ -1 +0,0 @@
c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b

View file

@ -1 +0,0 @@
190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812

View file

@ -1 +0,0 @@
254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c

View file

@ -1 +0,0 @@
8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1

View file

@ -1 +0,0 @@
969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d

View file

@ -1 +0,0 @@
921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43

View file

@ -1 +0,0 @@
7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e

View file

@ -1 +0,0 @@
75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7

View file

@ -1 +0,0 @@
ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55

View file

@ -1 +0,0 @@
0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0

View file

@ -1 +0,0 @@
f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006

View file

@ -1 +0,0 @@
45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60

View file

@ -1 +0,0 @@
0b7fd4a0bbc4b92641dbf20adfd7e3fd1398fe17102d94b674234563e110088a

View file

@ -1 +0,0 @@
868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a

View file

@ -1 +0,0 @@
34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c

View file

@ -1 +0,0 @@
d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8

View file

@ -1 +0,0 @@
87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572

View file

@ -1 +0,0 @@
b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce

View file

@ -1,27 +0,0 @@
Copyright 2019, the Dart project authors.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of Google LLC nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -1 +0,0 @@
file:/tools/OWNERS_MODEL

View file

@ -1,71 +0,0 @@
#!/usr/bin/env python3
# Copyright (c) 2024, 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.
"""CFE et al presubmit python script.
See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
for more details about the presubmit API built into gcl.
"""
import importlib.util
import importlib.machinery
import os.path
import subprocess
USE_PYTHON3 = True
def load_source(modname, filename):
loader = importlib.machinery.SourceFileLoader(modname, filename)
spec = importlib.util.spec_from_file_location(modname,
filename,
loader=loader)
module = importlib.util.module_from_spec(spec)
# The module is always executed and not cached in sys.modules.
# Uncomment the following line to cache the module.
# sys.modules[module.__name__] = module
loader.exec_module(module)
return module
def runSmokeTest(input_api, output_api):
local_root = input_api.change.RepositoryRoot()
utils = load_source('utils', os.path.join(local_root, 'tools', 'utils.py'))
dart = os.path.join(utils.CheckedInSdkPath(), 'bin', 'dart')
test_helper = os.path.join(local_root, 'pkg', 'front_end',
'presubmit_helper.dart')
windows = utils.GuessOS() == 'win32'
if windows:
dart += '.exe'
if not os.path.isfile(dart):
print('WARNING: dart not found: %s' % dart)
return []
if not os.path.isfile(test_helper):
print('WARNING: CFE et al presubmit_helper not found: %s' % test_helper)
return []
args = [dart, test_helper, input_api.PresubmitLocalPath()]
process = subprocess.Popen(args,
stdout=subprocess.PIPE,
stdin=subprocess.PIPE)
outs, _ = process.communicate()
if process.returncode != 0:
return [
output_api.PresubmitError('CFE et al presubmit script failure(s):',
long_text=outs)
]
return []
def CheckChangeOnCommit(input_api, output_api):
return runSmokeTest(input_api, output_api)
def CheckChangeOnUpload(input_api, output_api):
return runSmokeTest(input_api, output_api)

View file

@ -1,10 +0,0 @@
# FE/analyzer shared code
This package contains logic that is shared between the front_end and
analyzer packages. It is intended solely to facilitate development of
the Dart SDK, and is not intended for use by end users. In
particular, this package has no public API, so no guarantee is made of
compatibility between one version of the package and the next.
End users should consider using the analyzer package to analyze Dart
source code.

View file

@ -1,22 +0,0 @@
# Copyright (c) 2019, 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.
include: analysis_options_no_lints.yaml
linter:
rules:
- analyzer_public_api
- annotate_overrides
- collection_methods_unrelated_type
- curly_braces_in_flow_control_structures
- prefer_adjacent_string_concatenation
- unawaited_futures
- recursive_getters
- avoid_empty_else
- empty_statements
- valid_regexps
- lines_longer_than_80_chars
# - always_specify_types
- use_super_parameters
- comment_references

View file

@ -1,24 +0,0 @@
# Copyright (c) 2017, 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.
analyzer:
errors:
# Allow having TODOs in the code
todo: ignore
exclude:
- test/constants/data/**
- test/constants/data_2/**
- test/exhaustiveness/data/**
- test/flow_analysis/assigned_variables/data/**
- test/flow_analysis/definite_assignment/data/**
- test/flow_analysis/definite_unassignment/data/**
- test/flow_analysis/nullability/data/**
- test/flow_analysis/reachability/data/**
- test/flow_analysis/type_promotion/data/**
- test/flow_analysis/why_not_promoted/data/**
- test/inference/inferred_type_arguments/data/**
- test/inference/inferred_variable_types/data/**
- test/inheritance/data/**
- test/metadata/data/**
- test/metadata/evaluate_data/**

View file

@ -1,121 +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/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/static_type.dart';
import 'package:_fe_analyzer_shared/src/exhaustiveness/space.dart';
import '../../test/exhaustiveness/env.dart';
import '../../test/exhaustiveness/utils.dart';
void main() {
profile.enabled = true;
// (A)
// /|\
// B C D
var env = TestEnvironment();
var a = env.createClass('A', isSealed: true);
var b = env.createClass('B', inherits: [a]);
var c = env.createClass('C', inherits: [a]);
var d = env.createClass('D', inherits: [a]);
var t = env.createRecordType({'w': a, 'x': a, 'y': a, 'z': a});
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},
{'w': b, 'x': b, 'y': c, 'z': b},
{'w': b, 'x': b, 'y': c, 'z': c},
{'w': b, 'x': b, 'y': c, 'z': d},
{'w': b, 'x': b, 'y': d, 'z': b},
{'w': b, 'x': b, 'y': d, 'z': c},
{'w': b, 'x': b, 'y': d, 'z': d},
{'w': b, 'x': c, 'y': b, 'z': b},
{'w': b, 'x': c, 'y': b, 'z': c},
{'w': b, 'x': c, 'y': b, 'z': d},
{'w': b, 'x': c, 'y': c, 'z': b},
{'w': b, 'x': c, 'y': c, 'z': c},
{'w': b, 'x': c, 'y': c, 'z': d},
{'w': b, 'x': c, 'y': d, 'z': b},
{'w': b, 'x': c, 'y': d, 'z': c},
{'w': b, 'x': c, 'y': d, 'z': d},
{'w': b, 'x': d, 'y': b, 'z': b},
{'w': b, 'x': d, 'y': b, 'z': c},
{'w': b, 'x': d, 'y': b, 'z': d},
{'w': b, 'x': d, 'y': c, 'z': b},
{'w': b, 'x': d, 'y': c, 'z': c},
{'w': b, 'x': d, 'y': c, 'z': d},
{'w': b, 'x': d, 'y': d, 'z': b},
{'w': b, 'x': d, 'y': d, 'z': c},
{'w': b, 'x': d, 'y': d, 'z': d},
{'w': c, 'x': b, 'y': b, 'z': b},
{'w': c, 'x': b, 'y': b, 'z': c},
{'w': c, 'x': b, 'y': b, 'z': d},
{'w': c, 'x': b, 'y': c, 'z': b},
{'w': c, 'x': b, 'y': c, 'z': c},
{'w': c, 'x': b, 'y': c, 'z': d},
{'w': c, 'x': b, 'y': d, 'z': b},
{'w': c, 'x': b, 'y': d, 'z': c},
{'w': c, 'x': b, 'y': d, 'z': d},
{'w': c, 'x': c, 'y': b, 'z': b},
{'w': c, 'x': c, 'y': b, 'z': c},
{'w': c, 'x': c, 'y': b, 'z': d},
{'w': c, 'x': c, 'y': c, 'z': b},
{'w': c, 'x': c, 'y': c, 'z': c},
{'w': c, 'x': c, 'y': c, 'z': d},
{'w': c, 'x': c, 'y': d, 'z': b},
{'w': c, 'x': c, 'y': d, 'z': c},
{'w': c, 'x': c, 'y': d, 'z': d},
{'w': c, 'x': d, 'y': b, 'z': b},
{'w': c, 'x': d, 'y': b, 'z': c},
{'w': c, 'x': d, 'y': b, 'z': d},
{'w': c, 'x': d, 'y': c, 'z': b},
{'w': c, 'x': d, 'y': c, 'z': c},
{'w': c, 'x': d, 'y': c, 'z': d},
{'w': c, 'x': d, 'y': d, 'z': b},
{'w': c, 'x': d, 'y': d, 'z': c},
{'w': c, 'x': d, 'y': d, 'z': d},
{'w': d, 'x': b, 'y': b, 'z': b},
{'w': d, 'x': b, 'y': b, 'z': c},
{'w': d, 'x': b, 'y': b, 'z': d},
{'w': d, 'x': b, 'y': c, 'z': b},
{'w': d, 'x': b, 'y': c, 'z': c},
{'w': d, 'x': b, 'y': c, 'z': d},
{'w': d, 'x': b, 'y': d, 'z': b},
{'w': d, 'x': b, 'y': d, 'z': c},
{'w': d, 'x': b, 'y': d, 'z': d},
{'w': d, 'x': c, 'y': b, 'z': b},
{'w': d, 'x': c, 'y': b, 'z': c},
{'w': d, 'x': c, 'y': b, 'z': d},
{'w': d, 'x': c, 'y': c, 'z': b},
{'w': d, 'x': c, 'y': c, 'z': c},
{'w': d, 'x': c, 'y': c, 'z': d},
{'w': d, 'x': c, 'y': d, 'z': b},
{'w': d, 'x': c, 'y': d, 'z': c},
{'w': d, 'x': c, 'y': d, 'z': d},
{'w': d, 'x': d, 'y': b, 'z': b},
{'w': d, 'x': d, 'y': b, 'z': c},
{'w': d, 'x': d, 'y': b, 'z': d},
{'w': d, 'x': d, 'y': c, 'z': b},
{'w': d, 'x': d, 'y': c, 'z': c},
{'w': d, 'x': d, 'y': c, 'z': d},
{'w': d, 'x': d, 'y': d, 'z': b},
{'w': d, 'x': d, 'y': d, 'z': c},
{'w': d, 'x': d, 'y': d, 'z': d},
]);
}
/// 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(ObjectPropertyLookup objectFieldLookup,
StaticType type, List<Map<String, Object>> cases) {
var spaces = cases.map((c) => ty(type, c)).toList();
profile.reset();
print(
isExhaustive(objectFieldLookup, Space(const Path.root(), type), spaces));
profile.log();
}

View file

@ -1,134 +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 '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';
import '../../test/exhaustiveness/env.dart';
import '../../test/exhaustiveness/utils.dart';
void main() {
// (A)
// /|\
// B C D
var env = TestEnvironment();
var a = env.createClass('A', isSealed: true);
var b = env.createClass('B', inherits: [a]);
var c = env.createClass('C', inherits: [a]);
var d = env.createClass('D', inherits: [a]);
var t = env.createRecordType({'w': a, 'x': a, 'y': a, 'z': a});
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},
{'w': b, 'x': b, 'y': c, 'z': b},
{'w': b, 'x': b, 'y': c, 'z': c},
{'w': b, 'x': b, 'y': c, 'z': d},
{'w': b, 'x': b, 'y': d, 'z': b},
{'w': b, 'x': b, 'y': d, 'z': c},
{'w': b, 'x': b, 'y': d, 'z': d},
{'w': b, 'x': c, 'y': b, 'z': b},
{'w': b, 'x': c, 'y': b, 'z': c},
{'w': b, 'x': c, 'y': b, 'z': d},
{'w': b, 'x': c, 'y': c, 'z': b},
{'w': b, 'x': c, 'y': c, 'z': c},
{'w': b, 'x': c, 'y': c, 'z': d},
{'w': b, 'x': c, 'y': d, 'z': b},
{'w': b, 'x': c, 'y': d, 'z': c},
{'w': b, 'x': c, 'y': d, 'z': d},
{'w': b, 'x': d, 'y': b, 'z': b},
{'w': b, 'x': d, 'y': b, 'z': c},
{'w': b, 'x': d, 'y': b, 'z': d},
{'w': b, 'x': d, 'y': c, 'z': b},
{'w': b, 'x': d, 'y': c, 'z': c},
{'w': b, 'x': d, 'y': c, 'z': d},
{'w': b, 'x': d, 'y': d, 'z': b},
{'w': b, 'x': d, 'y': d, 'z': c},
{'w': b, 'x': d, 'y': d, 'z': d},
{'w': c, 'x': b, 'y': b, 'z': b},
{'w': c, 'x': b, 'y': b, 'z': c},
{'w': c, 'x': b, 'y': b, 'z': d},
{'w': c, 'x': b, 'y': c, 'z': b},
{'w': c, 'x': b, 'y': c, 'z': c},
{'w': c, 'x': b, 'y': c, 'z': d},
{'w': c, 'x': b, 'y': d, 'z': b},
{'w': c, 'x': b, 'y': d, 'z': c},
{'w': c, 'x': b, 'y': d, 'z': d},
{'w': c, 'x': c, 'y': b, 'z': b},
{'w': c, 'x': c, 'y': b, 'z': c},
{'w': c, 'x': c, 'y': b, 'z': d},
{'w': c, 'x': c, 'y': c, 'z': b},
{'w': c, 'x': c, 'y': c, 'z': c},
{'w': c, 'x': c, 'y': c, 'z': d},
{'w': c, 'x': c, 'y': d, 'z': b},
{'w': c, 'x': c, 'y': d, 'z': c},
{'w': c, 'x': c, 'y': d, 'z': d},
{'w': c, 'x': d, 'y': b, 'z': b},
{'w': c, 'x': d, 'y': b, 'z': c},
{'w': c, 'x': d, 'y': b, 'z': d},
{'w': c, 'x': d, 'y': c, 'z': b},
{'w': c, 'x': d, 'y': c, 'z': c},
{'w': c, 'x': d, 'y': c, 'z': d},
{'w': c, 'x': d, 'y': d, 'z': b},
{'w': c, 'x': d, 'y': d, 'z': c},
{'w': c, 'x': d, 'y': d, 'z': d},
{'w': d, 'x': b, 'y': b, 'z': b},
{'w': d, 'x': b, 'y': b, 'z': c},
{'w': d, 'x': b, 'y': b, 'z': d},
{'w': d, 'x': b, 'y': c, 'z': b},
{'w': d, 'x': b, 'y': c, 'z': c},
{'w': d, 'x': b, 'y': c, 'z': d},
{'w': d, 'x': b, 'y': d, 'z': b},
{'w': d, 'x': b, 'y': d, 'z': c},
{'w': d, 'x': b, 'y': d, 'z': d},
{'w': d, 'x': c, 'y': b, 'z': b},
{'w': d, 'x': c, 'y': b, 'z': c},
{'w': d, 'x': c, 'y': b, 'z': d},
{'w': d, 'x': c, 'y': c, 'z': b},
{'w': d, 'x': c, 'y': c, 'z': c},
{'w': d, 'x': c, 'y': c, 'z': d},
{'w': d, 'x': c, 'y': d, 'z': b},
{'w': d, 'x': c, 'y': d, 'z': c},
{'w': d, 'x': c, 'y': d, 'z': d},
{'w': d, 'x': d, 'y': b, 'z': b},
{'w': d, 'x': d, 'y': b, 'z': c},
{'w': d, 'x': d, 'y': b, 'z': d},
{'w': d, 'x': d, 'y': c, 'z': b},
{'w': d, 'x': d, 'y': c, 'z': c},
{'w': d, 'x': d, 'y': c, 'z': d},
{'w': d, 'x': d, 'y': d, 'z': b},
{'w': d, 'x': d, 'y': d, 'z': c},
{'w': d, 'x': d, 'y': d, 'z': d},
]);
}
/// 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(ObjectPropertyLookup 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 actual = isExhaustive(objectFieldLookup, valueSpace, caseSpaces);
if (!actual) {
throw 'Expected exhaustive';
}
}
var elapsed = watch.elapsedMilliseconds;
best = min(elapsed, best);
print('${elapsed / trials}ms (best ${best / trials}ms)');
}
}

View file

@ -1,100 +0,0 @@
// 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:_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/static_type.dart';
import 'package:_fe_analyzer_shared/src/exhaustiveness/space.dart';
import '../../test/exhaustiveness/env.dart';
import '../../test/exhaustiveness/utils.dart';
/// These tests show some pitfalls of the exhaustiveness checking algorithm
/// where the witness candidate count is exponential in the size of the user
/// code.
void main() {
profile.enabled = true;
for (int i = 1; i <= 30; i++) {
testSubtypeCount(i);
}
for (int i = 1; i <= 10; i++) {
testFieldCount(i);
}
}
/// Tests the how the number of subtypes affect the number of tested witness
/// candidates.
///
/// We create the sealed class A with n subtypes B1 to Bn:
///
/// (A)
/// / | \
/// B1 B2 ... Bn
///
/// with the trivial matching by a record type:
///
/// method(({A x, A y, A z, A w} r) => switch (r) {
/// (x: _, y: _, z: _, w: _) => 0,
/// };
///
/// which has the worst case `case count/subtype count` ratio for a fixed
/// pattern size.
void testSubtypeCount(int n) {
var env = TestEnvironment();
var a = env.createClass('A', isSealed: true);
for (int i = 1; i <= n; i++) {
env.createClass('B$i', inherits: [a]);
}
var t = env.createRecordType({'w': a, 'x': a, 'y': a, 'z': a});
expectExhaustive('Subtype count $n', env, t, [
{'w': a, 'x': a, 'y': a, 'z': a},
]);
}
/// Tests the how the number of pattern fields affect the number of tested
/// witness candidates.
///
/// We create the sealed class A with 5 subtypes B1 to B5:
///
/// (A)
/// / | \
/// B1 B2 ... B5
///
/// with the trivial matching by a record type with n fields:
///
/// method(({A a1, A a2, ..., A an} r) => switch (r) {
/// (a1: _, a2: _, ..., an: _) => 0,
/// };
///
/// which has the worst case `case count/runtime value count` ratio for a fixed
/// subtype count.
void testFieldCount(int n) {
var env = TestEnvironment();
var a = env.createClass('A', isSealed: true);
for (int i = 1; i <= 5; i++) {
env.createClass('B$i', inherits: [a]);
}
Map<String, StaticType> fields = {};
for (int i = 1; i <= n; i++) {
fields['a$i'] = a;
}
var t = env.createRecordType(fields);
expectExhaustive('Field count $n', env, t, [fields]);
}
void expectExhaustive(String title, ObjectPropertyLookup objectFieldLookup,
StaticType type, List<Map<String, Object>> cases) {
var spaces = cases.map((c) => ty(type, c)).toList();
profile.reset();
print(
isExhaustive(objectFieldLookup, Space(const Path.root(), type), spaces));
print('--------------------------------------------------------------------');
print(title);
profile.log();
}

View file

@ -1,27 +0,0 @@
// Copyright (c) 2025, 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.
/// Annotation for top level elements within a library that are part of the
/// analyzer's public API, even though they might not appear to be (e.g., due to
/// being implemented inside a `src` directory but re-exported in `lib`, or due
/// to being a supertype of a public type).
///
/// This annotation is intended to be used inside the `src` subdirectories of
/// the `_fe_analyzer_shared` and `analyzer` packages.
///
/// Applying this annotation to an element lets developers know that
/// modifications to the element should be carefully reviewed for backward
/// compatibility, and to make sure they don't unduly expose private
/// implementation details.
///
/// The `analyzer_public_api` lint rules use this annotation to tell:
/// - Which elements are safe to export in the analyzer's `lib` directory
/// - Which classes and mixins are safe to use as supertypes of a public type
class AnalyzerPublicApi {
/// Explanation of why this element is part of the public API, in spite of not
/// being in the analyzer's `lib` folder.
final String message;
const AnalyzerPublicApi({required this.message});
}

View file

@ -1,9 +0,0 @@
// Copyright (c) 2020, 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.
/// Map of error code unique name to custom correction.
const Map<String, String> customizedCorrections = {};
/// Map of error code unique name to custom message.
const Map<String, String> customizedMessages = {};

View file

@ -1,286 +0,0 @@
// Copyright (c) 2016, 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 'dart:math';
import 'package:_fe_analyzer_shared/src/base/analyzer_public_api.dart';
import 'customized_codes.dart';
/// An error code associated with an `AnalysisError`.
///
/// Generally, messages should follow the [Guide for Writing
/// Diagnostics](https://github.com/dart-lang/sdk/blob/main/pkg/front_end/lib/src/base/diagnostics.md).
@AnalyzerPublicApi(message: 'exported by package:analyzer/error/error.dart')
abstract class ErrorCode {
/// Regular expression for identifying positional arguments in error messages.
static final RegExp _positionalArgumentRegExp = new RegExp(r'{(\d+)\}');
/**
* The name of the error code.
*/
final String name;
/**
* The unique name of this error code.
*/
final String uniqueName;
final String _problemMessage;
final String? _correctionMessage;
/**
* Return `true` if diagnostics with this code have documentation for them
* that has been published.
*/
final bool hasPublishedDocs;
/**
* Whether this error is caused by an unresolved identifier.
*/
final bool isUnresolvedIdentifier;
/**
* Initialize a newly created error code to have the given [name]. The message
* associated with the error will be created from the given [problemMessage]
* template. The correction associated with the error will be created from the
* given [correctionMessage] template.
*/
const ErrorCode({
String? correctionMessage,
this.hasPublishedDocs = false,
this.isUnresolvedIdentifier = false,
required this.name,
required String problemMessage,
required this.uniqueName,
}) : _correctionMessage = correctionMessage,
_problemMessage = problemMessage;
/**
* The template used to create the correction to be displayed for this error,
* or `null` if there is no correction information for this error. The
* correction should indicate how the user can fix the error.
*/
String? get correctionMessage =>
customizedCorrections[uniqueName] ?? _correctionMessage;
/**
* The severity of the error.
*/
ErrorSeverity get errorSeverity;
/// Whether a finding of this error is ignorable via comments such as
/// `// ignore:` or `// ignore_for_file:`.
bool get isIgnorable => errorSeverity != ErrorSeverity.ERROR;
/**
* The template used to create the problem message to be displayed for this
* error. The problem message should indicate what is wrong and why it is
* wrong.
*/
String get problemMessage =>
customizedMessages[uniqueName] ?? _problemMessage;
int get numParameters {
int result = 0;
String? correctionMessage = _correctionMessage;
for (String s in [
_problemMessage,
if (correctionMessage != null) correctionMessage
]) {
for (RegExpMatch match in _positionalArgumentRegExp.allMatches(s)) {
result = max(result, int.parse(match.group(1)!) + 1);
}
}
return result;
}
/**
* The type of the error.
*/
ErrorType get type;
/**
* Return a URL that can be used to access documentation for diagnostics with
* this code, or `null` if there is no published documentation.
*/
String? get url {
if (hasPublishedDocs) {
return 'https://dart.dev/diagnostics/${name.toLowerCase()}';
}
return null;
}
@override
String toString() => uniqueName;
}
/**
* The severity of an [ErrorCode].
*/
@AnalyzerPublicApi(message: 'exported by package:analyzer/error/error.dart')
class ErrorSeverity implements Comparable<ErrorSeverity> {
/**
* The severity representing a non-error. This is never used for any error
* code, but is useful for clients.
*/
static const ErrorSeverity NONE = const ErrorSeverity('NONE', 0, " ", "none");
/**
* The severity representing an informational level analysis issue.
*/
static const ErrorSeverity INFO = const ErrorSeverity('INFO', 1, "I", "info");
/**
* The severity representing a warning. Warnings can become errors if the
* `-Werror` command line flag is specified.
*/
static const ErrorSeverity WARNING =
const ErrorSeverity('WARNING', 2, "W", "warning");
/**
* The severity representing an error.
*/
static const ErrorSeverity ERROR =
const ErrorSeverity('ERROR', 3, "E", "error");
static const List<ErrorSeverity> values = const [NONE, INFO, WARNING, ERROR];
/**
* The name of this error code.
*/
final String name;
/**
* The ordinal value of the error code.
*/
final int ordinal;
/**
* The name of the severity used when producing machine output.
*/
final String machineCode;
/**
* The name of the severity used when producing readable output.
*/
final String displayName;
/**
* Initialize a newly created severity with the given names.
*/
const ErrorSeverity(
this.name, this.ordinal, this.machineCode, this.displayName);
@override
int get hashCode => ordinal;
@override
int compareTo(ErrorSeverity other) => ordinal - other.ordinal;
/**
* Return the severity constant that represents the greatest severity.
*/
ErrorSeverity max(ErrorSeverity severity) =>
this.ordinal >= severity.ordinal ? this : severity;
@override
String toString() => name;
}
/**
* The type of an [ErrorCode].
*/
@AnalyzerPublicApi(message: 'exported by package:analyzer/error/error.dart')
class ErrorType implements Comparable<ErrorType> {
/**
* Task (todo) comments in user code.
*/
static const ErrorType TODO = const ErrorType('TODO', 0, ErrorSeverity.INFO);
/**
* Extra analysis run over the code to follow best practices, which are not in
* the Dart Language Specification.
*/
static const ErrorType HINT = const ErrorType('HINT', 1, ErrorSeverity.INFO);
/**
* Compile-time errors are errors that preclude execution. A compile time
* error must be reported by a Dart compiler before the erroneous code is
* executed.
*/
static const ErrorType COMPILE_TIME_ERROR =
const ErrorType('COMPILE_TIME_ERROR', 2, ErrorSeverity.ERROR);
/**
* Checked mode compile-time errors are errors that preclude execution in
* checked mode.
*/
static const ErrorType CHECKED_MODE_COMPILE_TIME_ERROR = const ErrorType(
'CHECKED_MODE_COMPILE_TIME_ERROR', 3, ErrorSeverity.ERROR);
/**
* Static warnings are those warnings reported by the static checker. They
* have no effect on execution. Static warnings must be provided by Dart
* compilers used during development.
*/
static const ErrorType STATIC_WARNING =
const ErrorType('STATIC_WARNING', 4, ErrorSeverity.WARNING);
/**
* Syntactic errors are errors produced as a result of input that does not
* conform to the grammar.
*/
static const ErrorType SYNTACTIC_ERROR =
const ErrorType('SYNTACTIC_ERROR', 6, ErrorSeverity.ERROR);
/**
* Lint warnings describe style and best practice recommendations that can be
* used to formalize a project's style guidelines.
*/
static const ErrorType LINT = const ErrorType('LINT', 7, ErrorSeverity.INFO);
static const List<ErrorType> values = const [
TODO,
HINT,
COMPILE_TIME_ERROR,
CHECKED_MODE_COMPILE_TIME_ERROR,
STATIC_WARNING,
SYNTACTIC_ERROR,
LINT,
];
/**
* The name of this error type.
*/
final String name;
/**
* The ordinal value of the error type.
*/
final int ordinal;
/**
* The severity of this type of error.
*/
final ErrorSeverity severity;
/**
* Initialize a newly created error type to have the given [name] and
* [severity].
*/
const ErrorType(this.name, this.ordinal, this.severity);
String get displayName => name.toLowerCase().replaceAll('_', ' ');
@override
int get hashCode => ordinal;
@override
int compareTo(ErrorType other) => ordinal - other.ordinal;
@override
String toString() => name;
}

View file

@ -1,30 +0,0 @@
// Copyright (c) 2016, 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/base/analyzer_public_api.dart';
/**
* Interface representing a syntactic entity (either a token or an AST node)
* which has a location and extent in the source file.
*/
@AnalyzerPublicApi(
message: 'exported by package:analyzer/dart/ast/syntactic_entity.dart')
abstract class SyntacticEntity {
/**
* Return the offset from the beginning of the file to the character after the
* last character of the syntactic entity.
*/
int get end;
/**
* Return the number of characters in the syntactic entity's source range.
*/
int get length;
/**
* Return the offset from the beginning of the file to the first character in
* the syntactic entity.
*/
int get offset;
}

View file

@ -1,121 +0,0 @@
// Copyright (c) 2025, 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 'dart:collection';
/// Debug helper that tracks updates to a property.
///
/// Use this in debugging to record stack traces for when a property is updated.
/// For instance to track the field `bar` change
///
/// class Foo {
/// Bar bar;
/// Foo(this.bar);
/// }
///
/// to
///
/// class Foo {
/// StackTraceValue<Bar> _bar;
/// Foo(Bar bar) : _bar = StackTraceValue(bar);
/// Bar get bar => _bar.value;
/// void set bar(Bar value) {
/// _bar.value = value;
/// }
/// }
///
class StackTraceValue<T> {
T _value;
List<(T, StackTrace)> _stackTraces = [];
StackTraceValue(this._value) {
_stackTraces.add((_value, StackTrace.current));
}
T get value => _value;
void set value(T v) {
_stackTraces.add((v, StackTrace.current));
_value = v;
}
}
/// Debug helper that tracks updates to a list.
///
/// Use this in debugging to record stack traces for when a list is updated.
/// For instance to track the list `bars` change
///
/// class Foo {
/// List<Bar> bars;
/// Foo(this.bars);
/// }
///
/// to
///
/// class Foo {
/// StackTraceList<Bar> _bars;
/// Foo(List<Bar> bars) : _bars = StackTraceList(bars);
/// List<Bar> get bars => _bars;
/// void set bars(List<Bar> value) {
/// _bars.value = value;
/// }
/// }
///
class StackTraceList<T> with ListMixin<T> implements List<T> {
List<T> _list;
List<(String, Object?, StackTrace)> _stackTraces = [];
StackTraceList(this._list) {
_stackTraces.add(('init', _list, StackTrace.current));
}
List<T> get value => _list;
void set value(List<T> v) {
_stackTraces.add(('value', v, StackTrace.current));
_list = v;
}
@override
void add(T element) {
_stackTraces.add(('add', element, StackTrace.current));
_list.add(element);
}
@override
void addAll(Iterable<T> iterable) {
_stackTraces.add(('addAll', iterable.toList(), StackTrace.current));
_list.addAll(iterable);
}
@override
void clear() {
_stackTraces.add(('clear', null, StackTrace.current));
_list.clear();
}
@override
int get length => _list.length;
@override
void set length(int newLength) {
_stackTraces.add(('length=', newLength, StackTrace.current));
_list.length = newLength;
}
@override
T operator [](int index) => _list[index];
@override
void operator []=(int index, T value) {
_stackTraces.add(('[$index]=', value, StackTrace.current));
_list[index] = value;
}
@override
bool remove(Object? element) {
_stackTraces.add(('remove', element, StackTrace.current));
return _list.remove(element);
}
}

View file

@ -1,181 +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/util/dependency_walker.dart';
/// Data structure tracking the type inference dependencies between generic
/// invocation parameters.
///
/// [planReconciliationStages] is used as part of support for
/// https://github.com/dart-lang/language/issues/731 (improved inference for
/// fold etc.) to choose the proper order in which to recursively analyze
/// function literals passed as invocation arguments.
abstract class FunctionLiteralDependencies<TypeVariable, ParamInfo,
DeferredParamInfo extends ParamInfo> {
final List<_Node<ParamInfo>> _paramNodes = [];
/// Construct a [FunctionLiteralDependencies] object that's prepared to
/// determine the order to resolve [deferredParams] for a generic invocation
/// involving the given [typeVariables].
///
/// [unDeferredParams] should contain information about any parameters
/// corresponding to arguments that have already been type inferred.
FunctionLiteralDependencies(
Iterable<DeferredParamInfo> deferredParams,
Iterable<TypeVariable> typeVariables,
Iterable<ParamInfo> unDeferredParams) {
Map<TypeVariable, Set<_Node<ParamInfo>>> paramsDependingOnTypeVar = {};
Map<TypeVariable, Set<_Node<ParamInfo>>> paramsConstrainingTypeVar = {};
int deferredParamIndex = 0;
for (DeferredParamInfo param in deferredParams) {
_Node<ParamInfo> paramNode =
new _Node<ParamInfo>(param, deferredParamIndex: deferredParamIndex++);
_paramNodes.add(paramNode);
for (TypeVariable v in typeVarsFreeInParamParams(param)) {
(paramsDependingOnTypeVar[v] ??= {}).add(paramNode);
}
for (TypeVariable v in typeVarsFreeInParamReturns(param)) {
(paramsConstrainingTypeVar[v] ??= {}).add(paramNode);
}
}
for (ParamInfo param in unDeferredParams) {
_Node<ParamInfo> paramNode =
new _Node<ParamInfo>(param, deferredParamIndex: null);
_paramNodes.add(paramNode);
// Note: for un-deferred parameters, we only care about
// typeVarsFreeInParamReturns, because these parameters have already been
// analyzed, so they can't depend on other parameters.
for (TypeVariable v in typeVarsFreeInParamReturns(param)) {
(paramsConstrainingTypeVar[v] ??= {}).add(paramNode);
}
}
for (TypeVariable typeVariable in typeVariables) {
for (_Node<ParamInfo> paramNode
in paramsDependingOnTypeVar[typeVariable] ?? const {}) {
paramNode.dependencies
.addAll(paramsConstrainingTypeVar[typeVariable] ?? const {});
}
}
}
/// Computes the order in which to resolve the deferred parameters passed to
/// the constructor.
///
/// Each entry in the returned list represents the set of parameters whose
/// corresponding arguments should be visited during a single stage of
/// resolution; after each stage, the assignment of actual types to type
/// variables should be refined. The list of parameters in each stage is
/// sorted to match the order of the `deferredParams` node passed to the
/// constructor.
///
/// So, for example, if the parameters in question are A, B, and C, and the
/// returned list is `[[A, B], [C]]`, then first parameters A and B should be
/// resolved, then the assignment of actual types to type variables should be
/// refined, and then C should be resolved, and then the final assignment of
/// actual types to type variables should be computed.
///
/// Note that the first stage may be empty; when this happens, it means that
/// the assignment of actual types to type variables should be refined before
/// doing any visiting.
List<List<DeferredParamInfo>> planReconciliationStages() {
_DependencyWalker<ParamInfo, DeferredParamInfo> walker =
new _DependencyWalker<ParamInfo, DeferredParamInfo>();
for (_Node<ParamInfo> paramNode in _paramNodes) {
walker.walk(paramNode);
}
List<_Node<ParamInfo>> _sortStage(List<_Node<ParamInfo>> stage) {
stage.sort((a, b) => a.deferredParamIndex! - b.deferredParamIndex!);
return stage;
}
return [
for (List<_Node<ParamInfo>> stage in walker.reconciliationStages)
[
for (_Node<ParamInfo> node in _sortStage(stage))
node.param as DeferredParamInfo
]
];
}
/// If the type of the parameter corresponding to [param] is a function type,
/// the set of type parameters referred to by the parameter types of that
/// parameter. If the type of the parameter is not a function type, an empty
/// iterable should be returned.
///
/// Should be overridden by the client.
Iterable<TypeVariable> typeVarsFreeInParamParams(DeferredParamInfo param);
/// If the type of the parameter corresponding to [param] is a function type,
/// the set of type parameters referred to by the return type of that
/// parameter. If the type of the parameter is not a function type, the set
/// type parameters referred to by the type of the parameter should be
/// returned.
///
/// Should be overridden by the client.
Iterable<TypeVariable> typeVarsFreeInParamReturns(ParamInfo param);
}
/// Derived class of [DependencyWalker] capable of walking the graph of type
/// inference dependencies among parameters.
class _DependencyWalker<ParamInfo, DeferredParamInfo extends ParamInfo>
extends DependencyWalker<_Node<ParamInfo>> {
/// The set of reconciliation stages accumulated so far.
final List<List<_Node<ParamInfo>>> reconciliationStages = [];
@override
void evaluate(_Node<ParamInfo> v) => evaluateScc([v]);
@override
void evaluateScc(List<_Node<ParamInfo>> nodes) {
int stageNum = 0;
for (_Node<ParamInfo> node in nodes) {
for (_Node<ParamInfo> dependency in node.dependencies) {
int? dependencyStageNum = dependency.stageNum;
if (dependencyStageNum != null && dependencyStageNum >= stageNum) {
stageNum = dependencyStageNum + 1;
}
}
}
if (reconciliationStages.length <= stageNum) {
reconciliationStages.add([]);
// `stageNum` can't grow by more than 1 each time `evaluateScc` is called,
// so adding one stage is sufficient to make sure the list is now long
// enough.
assert(stageNum < reconciliationStages.length);
}
List<_Node<ParamInfo>> stage = reconciliationStages[stageNum];
for (_Node<ParamInfo> node in nodes) {
node.stageNum = stageNum;
if (node.deferredParamIndex != null) {
stage.add(node);
}
}
}
}
/// Node type representing a single parameter for purposes of walking the graph
/// of type inference dependencies among parameters.
class _Node<ParamInfo> extends Node<_Node<ParamInfo>> {
/// The [ParamInfo] represented by this node.
final ParamInfo param;
/// If not `null`, the index of the reconciliation stage to which this
/// parameter has been assigned.
int? stageNum;
/// The nodes for the parameters depended on by this parameter.
final List<_Node<ParamInfo>> dependencies = [];
/// If this node represents a deferred parameter, the index of it in the list
/// of deferred parameters used to construct [FunctionLiteralDependencies].
final int? deferredParamIndex;
_Node(this.param, {required this.deferredParamIndex});
@override
bool get isEvaluated => stageNum != null;
@override
List<_Node<ParamInfo>> computeDependencies() => dependencies;
}

View file

@ -1,108 +0,0 @@
// 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.
/// An accumulator of a Dart code template, consisting of strings interspersed
/// with semantic nodes representing references to part of the user's program.
///
/// This is similar to a StringBuffer, except that:
/// - [write] requires a string argument (to prevent mistakes)
/// - additional methods are provided to allow writing semantic nodes that refer
/// to classes, enumerated values, etc. These semantic nodes will be
/// converted to simple strings at a later time.
///
/// Clients who do not need the additional semantic information may obtain the
/// final string immediately using [SimpleDartBuffer].
abstract class DartTemplateBuffer<ConstantValue extends Object,
EnumValue extends Object, Type extends Object> {
/// Adds a text string to the buffer.
void write(String text);
/// Adds a semantic node representing the given boolean value to the buffer.
void writeBoolValue(bool value);
/// Adds a semantic node representing the given core type to the buffer.
///
/// Ideally, callers should use [writeGeneralType] instead, since it allows
/// [Type] information to be associated with the semantic node. However, the
/// exhaustiveness algorithm currently uses this method for the core types
/// `Object`, `Never`, and `Null`, because its representation of those types
/// doesn't track the necessary [Type] semantics.
///
/// TODO(paulberry): add the necessary semantics tracking to the
/// exhaustiveness checker so that this method can be eliminated.
void writeCoreType(String name);
/// Adds a semantic node representing the given enumerated [value] to the
/// buffer.
///
/// [name] is a simple string representation of the enumerated value.
///
/// TODO(paulberry): consider replacing [SimpleDartBuffer] with a CFE
/// implementation of [DartTemplateBuffer] that knows how to look up the name
/// of the enum, so that we don't need the [name] parameter.
void writeEnumValue(EnumValue value, String name);
/// Adds a semantic node representing the given constant [value] to the
/// buffer.
///
/// [name] is a simple string representation of constant value.
///
/// This is used for any constants that are not enum values or booleans.
///
/// TODO(paulberry): investigate when this method is used. Is it possible
/// that [ConstantValue] might be a literal (and hence won't necessarily have
/// a name?)
void writeGeneralConstantValue(ConstantValue value, String name);
/// Adds a semantic node representing the given [type] to the buffer.
///
/// [name] is a simple string representation of the type.
///
/// TODO(paulberry): consider replacing [SimpleDartBuffer] with a CFE
/// implementation of [DartTemplateBuffer] that knows how to look up the name
/// of the type, so that we don't need the [name] parameter.
///
/// TODO(paulberry): what happens if the type is a typedef (e.g.
/// `typedef A = void Function();`).
void writeGeneralType(Type type, String name);
}
/// An accumulator of a Dart code template that discards semantic information,
/// immediately producing a simple string.
class SimpleDartBuffer implements DartTemplateBuffer<Object, Object, Object> {
final StringBuffer _buffer = new StringBuffer();
@override
String toString() => _buffer.toString();
@override
void write(String text) {
_buffer.write(text);
}
@override
void writeBoolValue(bool value) {
_buffer.write(value);
}
@override
void writeCoreType(String name) {
_buffer.write(name);
}
@override
void writeEnumValue(Object value, String name) {
_buffer.write(name);
}
@override
void writeGeneralConstantValue(Object value, String name) {
_buffer.write(name);
}
@override
void writeGeneralType(Object type, String name) {
_buffer.write(name);
}
}

View file

@ -1,446 +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 'static_type.dart';
import 'key.dart';
import 'path.dart';
import 'profile.dart' as profile;
import 'space.dart';
import 'witness.dart';
/// Returns `true` if [caseSpaces] exhaustively covers all possible values of
/// [valueSpace].
bool isExhaustive(ObjectPropertyLookup fieldLookup, Space valueSpace,
List<Space> caseSpaces) {
return checkExhaustiveness(fieldLookup, valueSpace, caseSpaces) == null;
}
/// Checks the [caseSpaces] 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.
///
/// [caseIsGuarded] should be a list of booleans indicating whether each case in
/// [caseSpaces] has an associated `when` clause.
///
/// If any unreachable cases are found, information about them is appended to
/// [caseUnreachabilities]. (If `null` is passed for [caseUnreachabilities],
/// then no information about unreachable cases is generated).
///
/// If the switch cases are not fully exhaustive, details about how they fail to
/// be exhaustive are returned using a data structure of type
/// [NonExhaustiveness]; otherwise `null` is returned.
///
/// Note that if a non-null value is returned, that doesn't necessarily mean
/// that an error should be reported; the caller still must check whether the
/// switch has a `default` clause and whether the scrutinee type is an "always
/// exhaustive" type.
NonExhaustiveness? computeExhaustiveness(ObjectPropertyLookup fieldLookup,
StaticType valueType, List<bool> caseIsGuarded, List<Space> caseSpaces,
{List<CaseUnreachability>? caseUnreachabilities}) {
_Checker checker = new _Checker(fieldLookup);
Space valuePattern = new Space(const Path.root(), valueType);
List<List<Space>> caseRows = [];
for (int i = 0; i < caseSpaces.length; i++) {
late List<Space> caseRow = [caseSpaces[i]];
if (caseUnreachabilities != null && i > 0) {
// See if this case is covered by previous ones.
if (checker._unmatched(caseRows, caseRow,
returnMultipleWitnesses: false) ==
null) {
caseUnreachabilities
.add(new CaseUnreachability(valueType, caseSpaces, i));
}
}
if (!caseIsGuarded[i]) {
caseRows.add(caseRow);
}
}
List<Witness>? witnesses = checker._unmatched(caseRows, [valuePattern],
returnMultipleWitnesses: true);
if (witnesses != null) {
return new NonExhaustiveness(valueType, caseSpaces, witnesses);
} else {
return null;
}
}
/// Determines if [cases] is exhaustive over all values contained by
/// [valueSpace]. If so, returns `null`. Otherwise, returns a list of [Witness]s
/// of values that aren't matched by anything in [cases].
List<Witness>? checkExhaustiveness(
ObjectPropertyLookup 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();
List<Witness>? witnesses = checker._unmatched(caseRows, [valueSpace],
returnMultipleWitnesses: true);
// Uncomment this to have it print out the witness for non-exhaustive matches.
// if (witnesses != null) witnesses.forEach(print);
return witnesses;
}
class _Checker {
final ObjectPropertyLookup _propertyLookup;
_Checker(this._propertyLookup);
/// 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].
List<Witness>? _unmatched(
List<List<Space>> caseRows, List<Space> valuePatterns,
{List<Predicate> witnessPredicates = const [],
required bool returnMultipleWitnesses}) {
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)];
} else if (caseRows.isEmpty && !returnMultipleWitnesses) {
// We have no more cases that can match the witness, so unless we want
// to multiple witnesses, we return directly.
//
// This will return the shortest possible witness and will for instance
// return `(E.b, _)` instead of `(E.b, E.a)` for
//
// enum E { a, b }
// m(E e) => switch (e) { (E.a, _) => 0, };
//
// and `C(f1: int())` instead of `C(f1: int(), f2: int())` for
//
// abstract class C { num f1, f2; }
// m(C c) => switch (e) { C(f1: double(), f2: double()) => 0, };
//
// Additionally, it avoids a degenerate case occurring only when checking
// for unreachable cases. In this mode, the value space is created from
// cases that are not included in the case rows. This means that the
// value space visited with an empty set of case rows left, can be
// exponential in the size of the case. For instance if we have
//
// sealed class S {}
// class S1 extends S {}
// class S2 extends S {
// String? f1, f2, f3, f4, f5, f6, f7, f8;
// }
// m(S s) => {
// S1() => 0,
// S2(:var f1, :var f2, :var f3, :var f4,
// :var f5, :var f6, :var f7, :var f8) => 1,
// };
//
// then in order to check that the second case is not unreachable after
// the first case, we would otherwise check all combinations of `S2` with
// fields values `String` or `null` for fields `f1` to `f8`, instead of
// just stopping with the shortest witness `S2()`.
//
// If we want to compute multiple witness, which we only do for the
// top-most value space, we continue the search for longer witnesses. This
// means that if we have
//
// enum E { a, b }
// m(E e) => switch (e) {};
//
// we do not simply return `_` as the witness, but instead the witnesses
// `E.a` and `E.b`. We want this for the quick-fix that adds all enum
// values or sealed class subclasses in case of an empty switch.
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.additionalProperties.keys);
}
}
for (SingleSpace firstValuePattern in firstValuePatterns.singleSpaces) {
StaticType contextType = firstValuePattern.type;
List<StaticType> stack = [firstValuePattern.type];
List<Witness>? witnesses;
while (stack.isNotEmpty) {
StaticType type = stack.removeAt(0);
if (type.isSubtypeOf(StaticType.neverType)) {
// Don't try to exhaust the Never type.
continue;
}
if (type.isSealed) {
List<Witness>? result = _filterByType(
contextType,
type,
caseRows,
firstValuePattern,
valuePatterns,
witnessPredicates,
firstValuePatterns.path,
// We don't use the witnesses, so only compute one.
returnMultipleWitnesses: false);
if (result == null) {
// This type was fully handled so no need to test its
// subtypes.
} else {
// The type was not fully handled so we must allow for
// handling of individual subtypes.
stack.addAll(type.getSubtypes(keysOfInterest));
}
} else {
List<Witness>? result = _filterByType(
contextType,
type,
caseRows,
firstValuePattern,
valuePatterns,
witnessPredicates,
firstValuePatterns.path,
// Don't collect multiple witnesses for to avoid combinatorial
// explosion. For instance returning
//
// (E.a, E.b), (E.a, E.c) ... (E.z, E.z) // 675 witnesses
//
// for
//
// enum E { a, b, ..., z }
// method((E, E) r) => switch (r) { (E.a, E.a) => 0, };
//
returnMultipleWitnesses: false);
// 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) {
(witnesses ??= []).addAll(result);
if (!returnMultipleWitnesses) {
return witnesses;
}
}
}
}
if (witnesses != null) {
return witnesses;
}
}
// If we get here, no subtype yielded a witness, so we must have matched
// everything.
return null;
}
List<Witness>? _filterByType(
StaticType contextType,
StaticType type,
List<List<Space>> caseRows,
SingleSpace firstSingleSpaceValue,
List<Space> valueSpaces,
List<Predicate> witnessPredicates,
Path path,
{required bool returnMultipleWitnesses}) {
profile.count('_filterByType');
// Extend the witness with the type we're matching.
List<Predicate> extendedWitness = [
...witnessPredicates,
new Predicate(path, contextType, 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> propertyKeys = {
...firstSingleSpaceValue.properties.keys,
for (SingleSpace firstPattern in remainingRowFirstSingleSpaces)
...firstPattern.properties.keys
};
Set<Key> additionalPropertyKeys = {
...firstSingleSpaceValue.additionalProperties.keys,
for (SingleSpace firstPattern in remainingRowFirstSingleSpaces)
...firstPattern.additionalProperties.keys
};
// Sorting isn't necessary, but makes the behavior deterministic.
List<Key> sortedPropertyKeys = propertyKeys.toList()..sort();
List<Key> sortedAdditionalPropertyKeys = additionalPropertyKeys.toList()
..sort();
// Remove the first column from the value list and replace it with any
// expanded fields.
valueSpaces = [
..._expandProperties(sortedPropertyKeys, sortedAdditionalPropertyKeys,
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] = [
..._expandProperties(
sortedPropertyKeys,
sortedAdditionalPropertyKeys,
remainingRowFirstSingleSpaces[i],
remainingRowFirstSingleSpaces[i].type,
path),
...remainingRows[i].skip(1)
];
}
// Proceed to the next column.
return _unmatched(remainingRows, valueSpaces,
witnessPredicates: extendedWitness,
returnMultipleWitnesses: returnMultipleWitnesses);
}
/// Given a list of [propertyKeys] and [additionalPropertyKeys], and a
/// [singleSpace], generates a list of single spaces, one for each named
/// property and additional property key.
///
/// When [singleSpace] contains a property with that name or an additional
/// property with the key, extracts it into the resulting list. Otherwise, the
/// [singleSpace] doesn't care about that property, so inserts a default
/// [Space] that matches all values for the property. If the [type] doesn't
/// know about the property, the static type of the property is read from
/// [Key].
///
/// In other words, this unpacks a set of properties so that the main
/// algorithm can add them to the worklist.
List<Space> _expandProperties(
List<Key> propertyKeys,
List<Key> additionalPropertyKeys,
SingleSpace singleSpace,
StaticType type,
Path path) {
profile.count('_expandProperties');
List<Space> result = <Space>[];
for (Key key in propertyKeys) {
Space? property = singleSpace.properties[key];
if (property != null) {
result.add(property);
} else {
// This pattern doesn't test this property, so add a pattern for the
// property that matches all values. This way the columns stay aligned.
StaticType? propertyType = type.getPropertyType(_propertyLookup, key);
if (propertyType == null && key is ExtensionKey) {
propertyType = key.type;
}
// TODO(johnniwinther): Enable this assert when extension members are
// handled.
/*assert(propertyType != null,
"Type $type does not have a type for property $key");*/
result.add(new Space(
path.add(key), propertyType ?? StaticType.nullableObject));
}
}
for (Key key in additionalPropertyKeys) {
Space? property = singleSpace.additionalProperties[key];
if (property != null) {
result.add(property);
} else {
// This pattern doesn't test this property, so add a pattern for the
// property that matches all values. This way the columns stay aligned.
// TODO(johnniwinther): Enable this assert when extension members are
// handled.
// assert(type.getAdditionalPropertyType(key) != null,
// "Type $type does not have a type for additional property $key");
result.add(new Space(path.add(key),
type.getAdditionalPropertyType(key) ?? StaticType.nullableObject));
}
}
return result;
}
}
/// Recursively expands [type] with its subtypes if it's 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();
}
}
List<StaticType> checkingOrder(StaticType type, Set<Key> keysOfInterest) {
List<StaticType> result = [];
List<StaticType> pending = [type];
while (pending.isNotEmpty) {
StaticType type = pending.removeAt(0);
result.add(type);
if (type.isSealed) {
pending.addAll(type.getSubtypes(keysOfInterest));
}
}
return result;
}
class NonExhaustiveness {
final StaticType valueType;
final List<Space> cases;
final List<Witness> witnesses;
NonExhaustiveness(this.valueType, this.cases, this.witnesses);
@override
String toString() =>
'$valueType is not exhaustively matched by ${cases.join('|')}.';
}
class CaseUnreachability {
final StaticType valueType;
final List<Space> cases;
final int index;
CaseUnreachability(this.valueType, this.cases, this.index);
@override
String toString() => 'Case #${index + 1} ${cases[index]} is unreachable.';
}

View file

@ -1,270 +0,0 @@
// 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 'static_type.dart';
/// 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, name and extension 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, name and extension 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, name and extension 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 if (other is ExtensionKey) {
// Name keys before extension keys.
return -1;
} 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 if (other is ExtensionKey) {
// Record index keys before extension 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);
}
class ExtensionKey implements Key {
final StaticType receiverType;
@override
final String name;
final StaticType type;
ExtensionKey(this.receiverType, this.name, this.type);
@override
int compareTo(Key other) {
if (other is ExtensionKey) {
// Sorting is only used for a stable choice of witness, so it's ok that in
// edge cases `receiverType.name` is not unique.
int result = receiverType.name.compareTo(other.receiverType.name);
if (result == 0) {
result = name.compareTo(other.name);
}
return result;
} else {
// Extension keys after other keys.
return 1;
}
}
@override
int get hashCode => Object.hash(receiverType, name);
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is ExtensionKey &&
receiverType == other.receiverType &&
name == other.name;
}
@override
String toString() => 'ExtensionKey($receiverType.$name:$type)';
}

View file

@ -1,87 +0,0 @@
// 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.
/// @docImport 'space.dart';
library;
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}';
}
}
}

View file

@ -1,47 +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 'dart:math';
bool enabled = false;
final _counts = <String, int>{};
void count(String name, [String? subname]) {
if (!enabled) return;
_counts.putIfAbsent(name, () => 0);
_counts[name] = _counts[name]! + 1;
if (subname != null) {
count('$name/$subname');
}
}
void log() {
List<String> names = _counts.keys.toList();
names.sort();
int nameLength =
names.fold<int>(0, (length, name) => max(length, name.length));
int countLength = _counts.values
.fold<int>(0, (length, count) => max(length, count.toString().length));
for (String name in names) {
print('${name.padRight(nameLength)} = '
'${_counts[name].toString().padLeft(countLength)}');
}
}
void reset() {
_counts.clear();
}
void run(void Function() callback) {
reset();
try {
callback();
} finally {
log();
reset();
}
}

View file

@ -1,860 +0,0 @@
// 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 nullable type.
bool isNullable(Type type);
/// Returns `true` if [type] is a potentially nullable type.
bool isPotentiallyNullable(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 the extension type erasure of [type].
///
/// This is [type] in which all occurrences of extension types have been
/// replaced with their representation type.
Type getExtensionTypeErasure(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);
/// Returns the bound of [type] if is a type variable or a promoted type
/// variable. Otherwise returns `null`.
Type? getTypeVariableBound(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>, ObjectPropertyLookup {
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 unique [StaticType]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) {
type = typeOperations.getExtensionTypeErasure(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;
bool extractNull = typeOperations.isNullable(type);
Type typeWithoutNull = type;
if (extractNull) {
// If [type] is nullable, we model the static type by creating the
// non-nullable equivalent and then add `Null` afterwards.
//
// For instance we model `int?` as `int|Null`.
typeWithoutNull = typeOperations.getNonNullable(type);
}
if (typeOperations.isBoolType(typeWithoutNull)) {
staticType = _boolStaticType;
} else if (typeOperations.isRecordType(typeWithoutNull)) {
staticType = new RecordStaticType(typeOperations, this, typeWithoutNull);
} else {
Type? futureOrTypeArgument =
typeOperations.getFutureOrTypeArgument(typeWithoutNull);
if (futureOrTypeArgument != null) {
StaticType typeArgument = getStaticType(futureOrTypeArgument);
StaticType futureType = getStaticType(
typeOperations.instantiateFuture(futureOrTypeArgument));
bool isImplicitlyNullable =
typeOperations.isNullable(futureOrTypeArgument);
staticType = new FutureOrStaticType(
typeOperations, this, typeWithoutNull, typeArgument, futureType,
isImplicitlyNullable: isImplicitlyNullable);
} else {
EnumClass? enumClass = enumOperations.getEnumClass(typeWithoutNull);
if (enumClass != null) {
staticType = new EnumStaticType(
typeOperations, this, typeWithoutNull, _getEnumInfo(enumClass));
} else {
Class? sealedClass =
_sealedClassOperations.getSealedClass(typeWithoutNull);
if (sealedClass != null) {
staticType = new SealedClassStaticType(
typeOperations,
this,
typeWithoutNull,
this,
_sealedClassOperations,
_getSealedClassInfo(sealedClass));
} else {
Type? listType = typeOperations.getListType(typeWithoutNull);
if (listType != null) {
staticType =
new ListTypeStaticType(typeOperations, this, typeWithoutNull);
} else {
bool isImplicitlyNullable =
typeOperations.isNullable(typeWithoutNull);
staticType = new TypeBasedStaticType(
typeOperations, this, typeWithoutNull,
isImplicitlyNullable: isImplicitlyNullable);
Type? bound = typeOperations.getTypeVariableBound(type);
if (bound != null) {
staticType =
new WrappedStaticType(getStaticType(bound), staticType);
}
}
}
}
}
}
if (extractNull) {
// Include the `Null` which extracted from [type] into [typeWithoutNull`.
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 GeneralValueStaticType<Type, Identity>(
typeOperations,
this,
nonNullable,
new IdentityRestriction<Identity>(uniqueValue),
textualRepresentation,
uniqueValue);
if (typeOperations.isNullable(type)) {
staticType = staticType.nullable;
}
return staticType;
}
/// Returns a [StaticType] of the list [type] with the given [restriction] .
StaticType getListStaticType(
Type type, ListTypeRestriction<Type> restriction) {
Type nonNullable = typeOperations.getNonNullable(type);
StaticType staticType = new ListPatternStaticType(
typeOperations, this, nonNullable, restriction, restriction.toString());
if (typeOperations.isNullable(type)) {
staticType = staticType.nullable;
}
return staticType;
}
/// Returns a [StaticType] of the map [type] with the given [restriction] .
StaticType getMapStaticType(Type type, MapTypeRestriction<Type> restriction) {
Type nonNullable = typeOperations.getNonNullable(type);
StaticType staticType = new MapPatternStaticType(
typeOperations, this, nonNullable, restriction, restriction.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;
ObjectPropertyLookup get objectFieldLookup;
/// Returns `true` if the current library has version greater than or equal
/// to `$major.$minor`.
bool hasLanguageVersion(int major, int minor);
/// 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 = staticType.nonNullable;
}
return staticType;
}
/// Creates the [StaticType] for the list [type] with the given [restriction].
StaticType createListType(Type type, ListTypeRestriction<Type> restriction);
/// Creates the [StaticType] for the map [type] with the given [restriction].
StaticType createMapType(Type type, MapTypeRestriction<Type> restriction);
/// 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) {
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,
Map<String, Type> extensionPropertyTypes,
{required bool nonNull}) {
StaticType staticType =
_createStaticTypeWithContext(contextType, type, nonNull: nonNull);
Map<Key, Space> properties = <Key, Space>{};
for (MapEntry<String, Pattern> entry in fieldPatterns.entries) {
String name = entry.key;
StaticType propertyType;
Type? extensionPropertyType = extensionPropertyTypes[name];
Key key;
if (extensionPropertyType != null) {
propertyType = createStaticType(extensionPropertyType);
key = new ExtensionKey(createStaticType(type), name, propertyType);
} else {
key = new NameKey(name);
propertyType = staticType.getPropertyType(objectFieldLookup, key) ??
StaticType.nullableObject;
}
properties[key] = dispatchPattern(
path.add(key), propertyType, entry.value,
nonNull: false);
}
return new Space(path, staticType, properties: properties);
}
/// Creates the [Space] at [path] for a record pattern of the required
/// [recordType], [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> properties = <Key, Space>{};
for (int index = 0; index < positionalFields.length; index++) {
Key key = new RecordIndexKey(index);
StaticType propertyType =
staticType.getPropertyType(objectFieldLookup, key) ??
StaticType.nullableObject;
properties[key] = dispatchPattern(
path.add(key), propertyType, positionalFields[index],
nonNull: false);
}
for (MapEntry<String, Pattern> entry in namedFields.entries) {
Key key = new RecordNameKey(entry.key);
StaticType propertyType =
staticType.getPropertyType(objectFieldLookup, key) ??
StaticType.nullableObject;
properties[key] = dispatchPattern(
path.add(key), propertyType, entry.value,
nonNull: false);
}
return new Space(path, staticType, properties: properties);
}
/// 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 = staticType.nonNullable;
}
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);
}
/// Returns `true` if [singleSpace] does not restrict the spaces of any of its
/// properties. For instance the pattern `Object(:int hashCode)` is
/// unrestricted but the pattern `Object(hashCode: 5)` is restricted.
bool _isUnrestricted(SingleSpace singleSpace) {
if (singleSpace.properties.isNotEmpty) {
Map<Key, StaticType> fieldTypes = singleSpace.type.fields;
for (MapEntry<Key, Space> entry in singleSpace.properties.entries) {
Key key = entry.key;
StaticType fieldType = fieldTypes[key] ?? StaticType.neverType;
if (!_isContainedIn(fieldType, entry.value)) {
return false;
}
}
}
if (singleSpace.additionalProperties.isNotEmpty) {
for (MapEntry<Key, Space> entry
in singleSpace.additionalProperties.entries) {
Key key = entry.key;
StaticType fieldType =
singleSpace.type.getAdditionalPropertyType(key) ??
StaticType.neverType;
if (!_isContainedIn(fieldType, entry.value)) {
return false;
}
}
}
return true;
}
/// Returns `true` if the space implied by [type], i.e. all values of [type]
/// regardless of properties, is contained in [space].
bool _isContainedIn(StaticType type, Space space) {
Map<SingleSpace, bool> unrestrictedCache = {};
if (space.singleSpaces.length == 1) {
// Optimize for simple spaces to avoid unnecessary expansion of subtypes.
SingleSpace singleSpace = space.singleSpaces.single;
bool isUnrestricted =
unrestrictedCache[singleSpace] ??= _isUnrestricted(singleSpace);
if (isUnrestricted && type.isSubtypeOf(singleSpace.type)) {
return true;
}
}
// To handle case like:
//
// sealed class M {}
// class A extends M {}
// class B extends M {}
// method(o) => switch (o) {
// (A() || B()) as M => 0,
// };
//
// we expand [type] into subtypes before determining for containment.
List<StaticType> subtypes = expandSealedSubtypes(type, const {});
for (StaticType subtype in subtypes) {
bool found = false;
for (SingleSpace singleSpace in space.singleSpaces) {
bool isUnrestricted =
unrestrictedCache[singleSpace] ??= _isUnrestricted(singleSpace);
if (isUnrestricted && subtype.isSubtypeOf(singleSpace.type)) {
found = true;
break;
}
}
if (!found) {
return false;
}
}
return true;
}
/// 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 (hasLanguageVersion(3, 3)) {
if (_isContainedIn(castType, space)) {
// If all values in [castType] are also in [space] then the complete
// [contextType] is matched. For instance
//
// method(var d) => switch (d) {
// final String value => value,
// // This covers everything either by matching as int or by
// // throwing.
// final value as int => value,
// };
space = new Space(path, contextType);
}
if (!typeOperations.isPotentiallyNullable(type)) {
// If `null` is _not_ included in [castType], we can include in the
// generated [space].
space = space.union(new Space(path, StaticType.nullType));
}
} else {
// The following check assumes that the subpattern space is
// unrestrictive.
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 = '';
}
ListTypeRestriction<Type> identity = new ListTypeRestriction(
elementType, typeArgumentText,
size: headSize + tailSize, hasRest: hasRest);
StaticType staticType = createListType(type, identity);
Map<Key, Space> additionalProperties = {};
for (int index = 0; index < headSize; index++) {
Key key = new HeadKey(index);
StaticType propertyType = staticType.getAdditionalPropertyType(key) ??
StaticType.nullableObject;
additionalProperties[key] = dispatchPattern(
path.add(key), propertyType, headElements[index],
nonNull: false);
}
if (hasRest) {
Key key = new RestKey(headSize, tailSize);
StaticType propertyType = staticType.getAdditionalPropertyType(key) ??
StaticType.nullableObject;
if (restElement != null) {
additionalProperties[key] = dispatchPattern(
path.add(key), propertyType, restElement,
nonNull: false);
} else {
additionalProperties[key] = new Space(path.add(key), propertyType);
}
}
for (int index = 0; index < tailSize; index++) {
Key key = new TailKey(index);
StaticType propertyType = staticType.getAdditionalPropertyType(key) ??
StaticType.nullableObject;
additionalProperties[key] = dispatchPattern(path.add(key), propertyType,
tailElements[tailElements.length - index - 1],
nonNull: false);
}
return new Space(path, staticType,
additionalProperties: additionalProperties);
}
/// 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 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 = '';
}
MapTypeRestriction<Type> identity = new MapTypeRestriction(
keyType, valueType, entries.keys.toSet(), typeArgumentsText);
StaticType staticType = createMapType(type, identity);
Map<Key, Space> additionalProperties = {};
for (MapEntry<Key, Pattern> entry in entries.entries) {
Key key = entry.key;
StaticType propertyType = staticType.getAdditionalPropertyType(key) ??
StaticType.nullableObject;
additionalProperties[key] = dispatchPattern(
path.add(key), propertyType, entry.value,
nonNull: false);
}
return new Space(path, staticType,
additionalProperties: additionalProperties);
}
/// 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> properties = {};
for (MapEntry<Key, Space> entry in a.properties.entries) {
Key key = entry.key;
Space aSpace = entry.value;
Space? bSpace = b.properties[key];
if (bSpace != null) {
properties[key] =
_createSpaceIntersection(path.add(key), aSpace, bSpace);
} else {
properties[key] = aSpace;
}
}
for (MapEntry<Key, Space> entry in b.properties.entries) {
Key key = entry.key;
properties[key] ??= entry.value;
}
return new SingleSpace(type, properties: properties);
}
/// 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);
}
}

View file

@ -1,121 +0,0 @@
// 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 'path.dart';
import 'static_type.dart';
/// 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 properties. The pattern then only
/// matches values where the property values are matched by the corresponding
/// property patterns.
class SingleSpace {
static final SingleSpace empty = new SingleSpace(StaticType.neverType);
/// The type of values the pattern matches.
final StaticType type;
/// Any property subpatterns the pattern matches.
final Map<Key, Space> properties;
/// Additional properties for map/list semantics.
final Map<Key, Space> additionalProperties;
SingleSpace(this.type,
{this.properties = const {}, this.additionalProperties = const {}});
@override
late final int hashCode = Object.hash(
type,
Object.hashAllUnordered(properties.keys),
Object.hashAllUnordered(properties.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 (properties.length != other.properties.length) return false;
if (properties.isNotEmpty) {
for (MapEntry<Key, Space> entry in properties.entries) {
if (entry.value != other.properties[entry.key]) {
return false;
}
}
}
return true;
}
@override
String toString() {
return type.spaceToText(properties, additionalProperties);
}
}
/// 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;
final List<SingleSpace> singleSpaces;
/// Create an empty space.
Space.empty(this.path) : singleSpaces = [SingleSpace.empty];
Space(Path path, StaticType type,
{Map<Key, Space> properties = const {},
Map<Key, Space> additionalProperties = const {}})
: this._(path, [
new SingleSpace(type,
properties: properties,
additionalProperties: additionalProperties)
]);
Space._(this.path, this.singleSpaces);
factory Space.fromSingleSpaces(Path path, List<SingleSpace> singleSpaces) {
Set<SingleSpace> singleSpacesSet = {};
for (SingleSpace singleSpace in singleSpaces) {
// Discard empty space.
if (singleSpace == SingleSpace.empty) {
continue;
}
singleSpacesSet.add(singleSpace);
}
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].properties.isEmpty &&
singleSpacesList[1].properties.isEmpty) {
singleSpacesList = [new SingleSpace(singleSpacesList[1].type.nullable)];
} else if (singleSpacesList[1].type == StaticType.nullType &&
singleSpacesList[1].properties.isEmpty &&
singleSpacesList[0].properties.isEmpty) {
singleSpacesList = [new SingleSpace(singleSpacesList[0].type.nullable)];
}
}
return new Space._(path, singleSpacesList);
}
Space union(Space other) {
return new Space.fromSingleSpaces(
path, [...singleSpaces, ...other.singleSpaces]);
}
@override
String toString() => singleSpaces.join('|');
}

View file

@ -1,497 +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 'dart_template_buffer.dart';
import 'key.dart';
import 'space.dart';
import 'witness.dart';
/// A static type in the type system.
abstract class StaticType {
/// Built-in top type that all types are a subtype of.
static const StaticType nullableObject =
const NullableStaticType(nonNullableObject);
/// Built-in top type that all types are a subtype of.
static const StaticType nonNullableObject = const _NonNullableObject();
/// Built-in `Null` type.
static const StaticType nullType = const _NullType(neverType);
/// Built-in `Never` type.
static const StaticType neverType = const _NeverType();
/// The static types of the fields this type exposes for record destructuring.
///
/// Includes inherited 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? getPropertyType(ObjectPropertyLookup 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? getAdditionalPropertyType(Key key);
/// Returns `true` if this static type is a subtype of [other], taking the
/// nullability and subtyping relation into account.
bool isSubtypeOf(StaticType other);
/// Whether this type is sealed. A sealed type is implicitly abstract and has
/// a closed set of known subtypes. This means that every instance of the
/// type must be an instance of one of those subtypes. Conversely, if an
/// instance is *not* an instance of one of those subtypes, that it must not
/// be an instance of this type.
///
/// Note that subtypes of a sealed type do not themselves have to be sealed.
/// Consider:
///
/// (A)
/// / \
/// B C
///
/// Here, A is sealed and B and C are not. There may be many unknown
/// subclasses of B and C, or classes implementing their interfaces. That
/// doesn't interfere with exhaustiveness checking because it's still the
/// case that any instance of A must be either a B or C *or some subtype of
/// one of those two types*.
bool get isSealed;
/// Returns `true` if this is a record type.
///
/// This is only used for print the type as part of a [Witness].
bool get isRecord;
/// Return `true` if this type is implicitly nullable.
///
/// This is used to omit the '?' for the [name] in the [NullableStaticType].
bool get isImplicitlyNullable;
/// Returns the name of this static type.
///
/// This is used for printing [Space]s.
String get name;
/// Writes the name of this static type to [buffer].
void typeToDart(DartTemplateBuffer buffer);
/// 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.
///
/// 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 [spaceProperties] and [additionalSpaceProperties].
String spaceToText(Map<Key, Space> spaceProperties,
Map<Key, Space> additionalSpaceProperties);
/// Write this [witness] with the [witnessFields] as a pattern into [buffer]
/// using this [StaticType] to determine the syntax.
///
/// If [forCorrection] is true, [witnessFields] that fully cover their static
/// type are omitted if possible.
void witnessToDart(DartTemplateBuffer buffer, PropertyWitness witness,
Map<Key, PropertyWitness> witnessFields,
{required bool forCorrection});
}
mixin _ObjectFieldMixin on _BaseStaticType {
@override
StaticType? getPropertyType(ObjectPropertyLookup fieldLookup, Key key) {
return fields[key] ?? fieldLookup.getObjectFieldType(key);
}
}
abstract class _BaseStaticType implements StaticType {
const _BaseStaticType();
@override
bool get isRecord => false;
@override
Map<Key, StaticType> get fields => const {};
@override
StaticType? getPropertyType(ObjectPropertyLookup fieldLookup, Key key) {
return fields[key];
}
@override
StaticType? getAdditionalPropertyType(Key key) => null;
@override
Iterable<StaticType> getSubtypes(Set<Key> keysOfInterest) => const [];
@override
bool isSubtypeOf(StaticType other) {
if (this == other) return true;
// All types are subtypes of Object?.
if (other == StaticType.nullableObject) return true;
if (other is WrappedStaticType) {
return isSubtypeOf(other.wrappedType) && isSubtypeOf(other.impliedType);
}
return false;
}
@override
String spaceToText(Map<Key, Space> spaceProperties,
Map<Key, Space> additionalSpaceProperties) {
assert(additionalSpaceProperties.isEmpty,
"Additional fields not supported in ${runtimeType}.");
if (this == StaticType.nullableObject && spaceProperties.isEmpty) {
return '()';
}
if (this == StaticType.neverType && spaceProperties.isEmpty) return '';
// If there are no fields, just show the type.
if (spaceProperties.isEmpty) return name;
StringBuffer buffer = new StringBuffer();
buffer.write(name);
buffer.write('(');
bool first = true;
spaceProperties.forEach((Key key, Space space) {
if (!first) buffer.write(', ');
if (key is ExtensionKey) {
buffer.write('${key.receiverType}.${key.name}: $space (${key.type})');
} else {
buffer.write('${key.name}: $space');
}
first = false;
});
buffer.write(')');
return buffer.toString();
}
@override
void witnessToDart(DartTemplateBuffer buffer, PropertyWitness witness,
Map<Key, PropertyWitness> witnessFields,
{required bool forCorrection}) {
if (this == StaticType.nullableObject && witnessFields.isEmpty) {
buffer.write('_');
} else if (this == StaticType.nullType && witnessFields.isEmpty) {
buffer.write('null');
} else {
typeToDart(buffer);
buffer.write('(');
String comma = '';
for (MapEntry<Key, PropertyWitness> entry in witnessFields.entries) {
Key key = entry.key;
PropertyWitness witness = entry.value;
buffer.write(comma);
comma = ', ';
buffer.write(key.name);
buffer.write(': ');
witness.witnessToDart(buffer, forCorrection: forCorrection);
}
buffer.write(')');
}
}
@override
String toString() => name;
}
class _NonNullableObject extends _BaseStaticType with _ObjectFieldMixin {
const _NonNullableObject();
@override
bool get isSealed => false;
@override
bool isSubtypeOf(StaticType other) {
// Object? is a subtype of itself and Object?.
return super.isSubtypeOf(other) || other == StaticType.nullableObject;
}
@override
String get name => 'Object';
@override
StaticType get nullable => StaticType.nullableObject;
@override
StaticType get nonNullable => this;
@override
bool get isImplicitlyNullable => false;
@override
void typeToDart(DartTemplateBuffer buffer) {
buffer.writeCoreType(name);
}
}
class _NeverType extends _BaseStaticType with _ObjectFieldMixin {
const _NeverType();
@override
bool get isSealed => false;
@override
bool isSubtypeOf(StaticType other) {
// Never is a subtype of all types.
return true;
}
@override
String get name => 'Never';
@override
StaticType get nullable => StaticType.nullType;
@override
StaticType get nonNullable => this;
@override
bool get isImplicitlyNullable => false;
@override
void typeToDart(DartTemplateBuffer buffer) {
buffer.writeCoreType(name);
}
}
class _NullType extends NullableStaticType with _ObjectFieldMixin {
const _NullType(super.underlying);
@override
bool get isSealed {
// Avoid splitting into [nullType] and [neverType].
return false;
}
@override
Iterable<StaticType> getSubtypes(Set<Key> keysOfInterest) {
// Avoid splitting into [nullType] and [neverType].
return const [];
}
@override
bool get isImplicitlyNullable => true;
@override
String get name => 'Null';
@override
void typeToDart(DartTemplateBuffer buffer) {
buffer.writeCoreType(name);
}
}
class NullableStaticType extends _BaseStaticType with _ObjectFieldMixin {
final StaticType underlying;
const NullableStaticType(this.underlying);
@override
bool get isSealed => true;
@override
Iterable<StaticType> getSubtypes(Set<Key> keysOfInterest) =>
[underlying, StaticType.nullType];
@override
bool isSubtypeOf(StaticType other) {
if (super.isSubtypeOf(other)) {
return true;
}
// A nullable type is a subtype if the underlying type and Null both are.
return this == other ||
other is NullableStaticType && underlying.isSubtypeOf(other.underlying);
}
@override
String get name =>
underlying.isImplicitlyNullable ? underlying.name : '${underlying.name}?';
@override
bool get isImplicitlyNullable => true;
@override
StaticType get nullable => this;
@override
StaticType get nonNullable => underlying;
@override
int get hashCode => underlying.hashCode * 11;
@override
bool operator ==(other) {
if (identical(this, other)) return true;
return other is NullableStaticType && underlying == other.underlying;
}
@override
void typeToDart(DartTemplateBuffer buffer) {
underlying.typeToDart(buffer);
if (!underlying.isImplicitlyNullable) {
buffer.write('?');
}
}
}
abstract class NonNullableStaticType extends _BaseStaticType {
@override
late final StaticType nullable = new NullableStaticType(this);
@override
StaticType get nonNullable => this;
@override
bool isSubtypeOf(StaticType other) {
if (super.isSubtypeOf(other)) return true;
// All non-nullable types are subtypes of Object.
if (other == StaticType.nonNullableObject) return true;
// A non-nullable type is a subtype of the underlying type of a nullable
// type.
if (other is NullableStaticType) {
return isSubtypeOf(other.underlying);
}
return isSubtypeOfInternal(other);
}
bool isSubtypeOfInternal(StaticType other);
@override
String toString() => name;
}
/// Static type the behaves like [wrappedType] but is also a subtype of
/// [impliedType].
class WrappedStaticType extends _BaseStaticType {
final StaticType wrappedType;
final StaticType impliedType;
WrappedStaticType(this.wrappedType, this.impliedType);
@override
Map<Key, StaticType> get fields => wrappedType.fields;
@override
StaticType? getPropertyType(ObjectPropertyLookup fieldLookup, Key key) {
return wrappedType.getPropertyType(fieldLookup, key);
}
@override
bool get isRecord => wrappedType.isRecord;
@override
bool get isSealed => wrappedType.isSealed;
@override
String get name => wrappedType.name;
@override
bool get isImplicitlyNullable => wrappedType.isImplicitlyNullable;
@override
Iterable<StaticType> getSubtypes(Set<Key> keysOfInterest) {
StaticType wrappedType = this.wrappedType;
StaticType impliedType = this.impliedType;
if (wrappedType is NullableStaticType &&
impliedType is NullableStaticType) {
// With nullable types we need to avoid carrying the nullable implied type
// into the non-nullable subtype since it otherwise wouldn't allow for
// matching the non-nullable aspect of the wrapped type with the
// non-nullable implied type.
//
// For instance
//
// method<O>(O? object) => switch (object) {
// O object => 0,
// null => 1,
// };
//
// Here the static type of `O?` is `WrappedStaticType(Object?, O?)` which
// allows for matching both by the bound `Object?` and the exact type
// variable type `O?`. If we split this into the subtypes
// `WrappedStaticType(Object, O?)` and `WrappedStaticType(null, O?)` then
// we miss that `O object` covers the non-nullable aspect, since `O` is
// neither a super type of `Object` nor `O?`.
return [
new WrappedStaticType(wrappedType.underlying, impliedType.underlying),
StaticType.nullType
];
}
return wrappedType
.getSubtypes(keysOfInterest)
.map((e) => new WrappedStaticType(e, impliedType));
}
@override
bool isSubtypeOf(StaticType other) {
if (super.isSubtypeOf(other)) return true;
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 witnessToDart(DartTemplateBuffer buffer, PropertyWitness witness,
Map<Key, PropertyWitness> witnessFields,
{required bool forCorrection}) {
return wrappedType.witnessToDart(buffer, witness, witnessFields,
forCorrection: forCorrection);
}
@override
late final StaticType nonNullable = wrappedType.nonNullable == wrappedType &&
impliedType.nonNullable == impliedType
? this
: new WrappedStaticType(wrappedType.nonNullable, impliedType.nonNullable);
@override
late final StaticType nullable =
wrappedType.nullable == wrappedType && impliedType.nullable == impliedType
? this
: new WrappedStaticType(wrappedType.nullable, impliedType.nullable);
@override
void typeToDart(DartTemplateBuffer buffer) {
wrappedType.typeToDart(buffer);
}
}
/// Interface for accessing the members defined on `Object`.
abstract class ObjectPropertyLookup {
/// Returns the [StaticType] for the member with the given [key] defined on
/// `Object`, or `null` none exists.
StaticType? getObjectFieldType(Key key);
}

View file

@ -1,99 +0,0 @@
// 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:_fe_analyzer_shared/src/exhaustiveness/witness.dart';
import 'exhaustive.dart';
import 'key.dart';
import 'space.dart';
import 'static_type.dart';
/// Tags used for id-testing of exhaustiveness.
class Tags {
static const String error = 'error';
static const String scrutineeType = 'type';
static const String scrutineeFields = 'fields';
static const String space = 'space';
static const String subtypes = 'subtypes';
static const String expandedSubtypes = 'expandedSubtypes';
static const String checkingOrder = 'checkingOrder';
}
/// Returns a textual representation for [space] used for testing.
String spacesToText(Space space) {
String text = space.toString();
if (text.startsWith('[') && (text.endsWith(']') || text.endsWith(']?'))) {
// Avoid list-like syntax which collides with the [Features] encoding.
return '<$text>';
}
return text;
}
/// Returns a textual representation for [fieldsOfInterest] used for testing.
String fieldsToText(StaticType type, ObjectPropertyLookup objectFieldLookup,
Set<Key> fieldsOfInterest) {
List<Key> sortedNames = fieldsOfInterest.toList()..sort();
StringBuffer sb = new StringBuffer();
String comma = '';
sb.write('{');
for (Key key in sortedNames) {
sb.write(comma);
if (key is ExtensionKey) {
sb.write(key.receiverType);
sb.write('.');
sb.write(key.name);
sb.write(':');
sb.write(staticTypeToText(key.type));
} else {
StaticType? fieldType = type.getPropertyType(objectFieldLookup, key);
sb.write(key.name);
sb.write(':');
if (fieldType != null) {
sb.write(staticTypeToText(fieldType));
} else {
sb.write("-");
}
}
comma = ',';
}
sb.write('}');
return sb.toString();
}
/// Returns a textual representation for [type] used for testing.
String staticTypeToText(StaticType type) => type.toString();
/// Returns a textual representation of [types] used for testing.
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 types) {
sb.write(comma);
sb.write(staticTypeToText(subtype));
comma = ',';
}
sb.write('}');
return sb.toString();
}
String nonExhaustivenessToText(NonExhaustiveness nonExhaustiveness) {
StringBuffer sb = new StringBuffer();
sb.write('non-exhaustive:');
String delimiter = '';
for (Witness witness in nonExhaustiveness.witnesses) {
sb.write(delimiter);
String witnessText = witness.asWitness;
String correctionText = witness.asCorrection;
if (witnessText != correctionText) {
sb.write('$witnessText/$correctionText');
} else {
sb.write(witnessText);
}
delimiter = ';';
}
return sb.toString();
}

View file

@ -1,217 +0,0 @@
// 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 'dart_template_buffer.dart';
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;
@override
final bool isImplicitlyNullable;
TypeBasedStaticType(this._typeOperations, this._fieldLookup, this._type,
{required this.isImplicitlyNullable});
@override
Map<Key, StaticType> get fields => _fieldLookup.getFieldTypes(_type);
@override
StaticType? getAdditionalPropertyType(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 witnessToDart(DartTemplateBuffer buffer, PropertyWitness witness,
Map<Key, PropertyWitness> witnessFields,
{required bool forCorrection}) {
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, PropertyWitness> 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(': ');
PropertyWitness field = entry.value;
field.witnessToDart(buffer, forCorrection: forCorrection);
}
}
buffer.write(additionalEnd);
} else {
super.witnessToDart(buffer, witness, witnessFields,
forCorrection: forCorrection);
}
}
@override
void typeToDart(DartTemplateBuffer buffer) {
buffer.writeGeneralType(_type, name);
}
}
/// [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)
: super(isImplicitlyNullable: false);
}
/// [StaticType] for an object restricted to a single value, where that value is
/// a general-purpose value (not a boolean or an enum).
class GeneralValueStaticType<Type extends Object, T extends Object>
extends ValueStaticType<Type, T> {
final T _value;
@override
void valueToDart(DartTemplateBuffer buffer) {
buffer.writeGeneralConstantValue(_value, name);
}
GeneralValueStaticType(super.typeOperations, super.fieldLookup, super.type,
super.restriction, super.name, this._value);
}
/// [StaticType] for an object restricted to a single value.
abstract class ValueStaticType<Type extends Object, T extends Object>
extends RestrictedStaticType<Type, IdentityRestriction<T>> {
void valueToDart(DartTemplateBuffer buffer);
ValueStaticType(super.typeOperations, super.fieldLookup, super.type,
super.restriction, super.name);
@override
void witnessToDart(DartTemplateBuffer buffer, PropertyWitness witness,
Map<Key, PropertyWitness> witnessFields,
{required bool forCorrection}) {
valueToDart(buffer);
// If we have restrictions on the value we create an and pattern.
String additionalStart = ' && Object(';
String additionalEnd = '';
String comma = '';
for (MapEntry<Key, PropertyWitness> 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(': ');
PropertyWitness field = entry.value;
field.witnessToDart(buffer, forCorrection: forCorrection);
}
}
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;
}

View file

@ -1,41 +0,0 @@
// 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)
: super(isImplicitlyNullable: false);
@override
bool get isSealed => true;
late StaticType trueType = new _BoolValueStaticType<Type>(
_typeOperations, _fieldLookup, _type, true);
late StaticType falseType = new _BoolValueStaticType<Type>(
_typeOperations, _fieldLookup, _type, false);
@override
Iterable<StaticType> getSubtypes(Set<Key> keysOfInterest) =>
[trueType, falseType];
}
/// [StaticType] for an object restricted to a single boolean value (either
/// `true` or `false`).
class _BoolValueStaticType<Type extends Object>
extends ValueStaticType<Type, bool> {
final bool _value;
_BoolValueStaticType(TypeOperations<Type> typeOperations,
FieldLookup<Type> fieldLookup, Type type, this._value)
: super(typeOperations, fieldLookup, type,
new IdentityRestriction<bool>(_value), '$_value');
@override
void valueToDart(DartTemplateBuffer buffer) {
buffer.writeBoolValue(_value);
}
}

View file

@ -1,149 +0,0 @@
// 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);
if (value != null) {
elements[value] = new EnumElementStaticType<Type, EnumElement>(
_typeOperations,
_fieldLookup,
_enumOperations.getEnumElementType(element),
new IdentityRestriction<EnumElement>(element),
_enumOperations.getEnumElementName(element),
element);
}
}
return elements;
}
}
/// [StaticType] for an instantiation of an enum that support access to the
/// enum values that populate its type through the [getSubtypes] getter.
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)
: super(isImplicitlyNullable: false);
@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> {
final EnumElement _value;
EnumElementStaticType(super.typeOperations, super.fieldLookup, super.type,
super.restriction, super.name, this._value);
@override
void valueToDart(DartTemplateBuffer buffer) {
buffer.writeEnumValue(_value, name);
}
}

View file

@ -1,28 +0,0 @@
// 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,
{required super.isImplicitlyNullable});
@override
bool get isSealed => true;
@override
Iterable<StaticType> getSubtypes(Set<Key> keysOfInterest) =>
[_typeArgument, _futureType];
}

View file

@ -1,279 +0,0 @@
// 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)
: super(isImplicitlyNullable: false);
@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++) {
ListTypeRestriction<Type> identity = new ListTypeRestriction(
elementType, typeArgumentText,
size: size, hasRest: false);
subtypes.add(new ListPatternStaticType<Type>(
_typeOperations, _fieldLookup, _type, identity, identity.toString()));
}
ListTypeRestriction<Type> identity = new ListTypeRestriction(
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 [ListTypeRestriction] for its
/// uniqueness.
class ListPatternStaticType<Type extends Object>
extends RestrictedStaticType<Type, ListTypeRestriction<Type>> {
ListPatternStaticType(super.typeOperations, super.fieldLookup, super.type,
super.restriction, super.name);
@override
String spaceToText(Map<Key, Space> spaceProperties,
Map<Key, Space> additionalSpaceProperties) {
StringBuffer buffer = new StringBuffer();
buffer.write(restriction.typeArgumentText);
buffer.write('[');
bool first = true;
additionalSpaceProperties.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 witnessToDart(DartTemplateBuffer buffer, PropertyWitness witness,
Map<Key, PropertyWitness> witnessFields,
{required bool forCorrection}) {
int maxHeadSize = 0;
int maxTailSize = 0;
PropertyWitness? restWitness;
for (MapEntry<Key, PropertyWitness> 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);
PropertyWitness? witness = witnessFields[key];
if (witness != null) {
witness.witnessToDart(buffer, forCorrection: forCorrection);
} else {
buffer.write('_');
}
comma = ', ';
}
if (restriction.hasRest) {
buffer.write(comma);
buffer.write('...');
if (restWitness != null) {
restWitness.witnessToDart(buffer, forCorrection: forCorrection);
}
comma = ', ';
}
for (int index = maxTailSize - 1; index >= 0; index--) {
buffer.write(comma);
Key key = new TailKey(index);
PropertyWitness? witness = witnessFields[key];
if (witness != null) {
witness.witnessToDart(buffer, forCorrection: forCorrection);
} 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, PropertyWitness> 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(': ');
PropertyWitness field = entry.value;
field.witnessToDart(buffer, forCorrection: forCorrection);
}
}
buffer.write(additionalEnd);
}
}
/// Restriction 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 ListTypeRestriction<Type extends Object> implements Restriction<Type> {
final Type elementType;
final int size;
final bool hasRest;
final String typeArgumentText;
ListTypeRestriction(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 ListTypeRestriction<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! ListTypeRestriction<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();
}
}

View file

@ -1,137 +0,0 @@
// 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 [MapTypeRestriction] for its
/// uniqueness.
class MapPatternStaticType<Type extends Object>
extends RestrictedStaticType<Type, MapTypeRestriction<Type>> {
MapPatternStaticType(super.typeOperations, super.fieldLookup, super.type,
super.restriction, super.name);
@override
String spaceToText(Map<Key, Space> spaceProperties,
Map<Key, Space> additionalSpaceProperties) {
StringBuffer buffer = new StringBuffer();
buffer.write(restriction.typeArgumentsText);
buffer.write('{');
bool first = true;
additionalSpaceProperties.forEach((Key key, Space space) {
if (!first) buffer.write(', ');
buffer.write('$key: $space');
first = false;
});
buffer.write('}');
return buffer.toString();
}
@override
void witnessToDart(DartTemplateBuffer buffer, PropertyWitness witness,
Map<Key, PropertyWitness> witnessFields,
{required bool forCorrection}) {
buffer.write('{');
String comma = '';
for (MapKey key in restriction.keys) {
buffer.write(comma);
buffer.write(key.valueAsText);
buffer.write(': ');
PropertyWitness? witness = witnessFields[key];
if (witness != null) {
witness.witnessToDart(buffer, forCorrection: forCorrection);
} 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, PropertyWitness> 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(': ');
PropertyWitness field = entry.value;
field.witnessToDart(buffer, forCorrection: forCorrection);
}
}
buffer.write(additionalEnd);
}
}
/// Restriction 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 MapTypeRestriction<Type extends Object> implements Restriction<Type> {
final Type keyType;
final Type valueType;
final Set<MapKey> keys;
final String typeArgumentsText;
MapTypeRestriction(
this.keyType, this.valueType, this.keys, this.typeArgumentsText);
@override
late final int hashCode =
Object.hash(keyType, valueType, Object.hashAllUnordered(keys));
@override
bool get isUnrestricted {
// The map pattern containing only a rest pattern covers the whole type.
return keys.isEmpty;
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
if (other is! MapTypeRestriction<Type>) return false;
if (keyType != other.keyType || valueType != other.valueType) {
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! MapTypeRestriction<Type>) return false;
if (!typeOperations.isSubtypeOf(keyType, other.keyType)) return false;
if (!typeOperations.isSubtypeOf(valueType, other.valueType)) return false;
return 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 = ', ';
}
sb.write('}');
return sb.toString();
}
}

View file

@ -1,114 +0,0 @@
// 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.
class RecordStaticType<Type extends Object> extends TypeBasedStaticType<Type> {
RecordStaticType(super.typeOperations, super.fieldLookup, super.type)
: super(isImplicitlyNullable: false);
@override
bool get isRecord => true;
@override
String spaceToText(Map<Key, Space> spaceProperties,
Map<Key, Space> additionalSpaceProperties) {
StringBuffer buffer = new StringBuffer();
buffer.write('(');
String comma = '';
fields.forEach((Key key, StaticType staticType) {
if (key is RecordIndexKey) {
buffer.write(comma);
comma = ', ';
buffer.write('${spaceProperties[key] ?? staticType}');
} else if (key is RecordNameKey) {
buffer.write(comma);
comma = ', ';
buffer.write('${key.name}: ${spaceProperties[key] ?? staticType}');
}
});
buffer.write(')');
String additionalStart = '(';
String additionalEnd = '';
comma = '';
spaceProperties.forEach((Key key, Space value) {
if (key is! RecordKey) {
buffer.write(additionalStart);
additionalStart = '';
additionalEnd = ')';
buffer.write(comma);
comma = ', ';
buffer.write('${key.name}: $value');
}
});
additionalSpaceProperties.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 witnessToDart(DartTemplateBuffer buffer, PropertyWitness witness,
Map<Key, PropertyWitness> witnessFields,
{required bool forCorrection}) {
buffer.write('(');
String comma = '';
for (Key key in fields.keys) {
if (key is RecordIndexKey) {
buffer.write(comma);
comma = ', ';
PropertyWitness? field = witnessFields[key];
if (field != null) {
field.witnessToDart(buffer, forCorrection: forCorrection);
} else {
buffer.write('_');
}
} else if (key is RecordNameKey) {
buffer.write(comma);
comma = ', ';
buffer.write(key.name);
buffer.write(': ');
PropertyWitness? field = witnessFields[key];
if (field != null) {
field.witnessToDart(buffer, forCorrection: forCorrection);
} 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, PropertyWitness> 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(': ');
PropertyWitness field = entry.value;
field.witnessToDart(buffer, forCorrection: forCorrection);
}
}
buffer.write(additionalEnd);
}
}

View file

@ -1,109 +0,0 @@
// 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)
: super(isImplicitlyNullable: false);
@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;
}
}

View file

@ -1,153 +0,0 @@
// 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 'dart_template_buffer.dart';
import 'key.dart';
import 'path.dart';
import 'static_type.dart';
/// 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 Path path;
/// The static type of the context. [valueType] is a subtype of this type.
final StaticType staticType;
/// The type this predicate tests.
// TODO(johnniwinther): In order to model exhaustiveness on enum types,
// bool values, and maybe integers at some point, we may later want a separate
// kind of predicate that means "this value was equal to this constant".
final StaticType valueType;
Predicate(this.path, this.staticType, this.valueType);
@override
String toString() => 'Predicate(path=$path,type=$valueType)';
}
/// 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:
///
/// [] is U
/// ['w'] is T
/// ['w', 'x'] is B
/// ['w', 'y'] is B
/// ['z'] is T
/// ['z', 'x'] is C
/// ['z', 'y'] is B
///
/// the [toString] produces:
///
/// 'U(w: T(x: B, y: B), z: T(x: C, y: B))'
class Witness {
final List<Predicate> _predicates;
late final PropertyWitness _witness = _buildWitness();
Witness(this._predicates);
PropertyWitness _buildWitness() {
PropertyWitness witness = new PropertyWitness();
for (Predicate predicate in _predicates) {
PropertyWitness here = witness;
for (Key field in predicate.path.toList()) {
here = here.properties.putIfAbsent(field, () => new PropertyWitness());
}
here.staticType = predicate.staticType;
here.valueType = predicate.valueType;
}
return witness;
}
String get asWitness => _witness.asWitness;
String get asCorrection => _witness.asCorrection;
/// Writes a representation of this witness to the given [buffer].
void toDart(DartTemplateBuffer buffer, {required bool forCorrection}) {
_witness.witnessToDart(buffer, forCorrection: forCorrection);
}
@override
String toString() => _witness.toString();
}
/// Helper class used to turn a list of [Predicate]s into a string.
class PropertyWitness {
StaticType staticType = StaticType.nullableObject;
StaticType valueType = StaticType.nullableObject;
final Map<Key, PropertyWitness> properties = {};
void witnessToDart(DartTemplateBuffer buffer, {required bool forCorrection}) {
if (properties.isNotEmpty) {
Map<StaticType, Map<Key, PropertyWitness>> witnessFieldsByType = {};
for (MapEntry<Key, PropertyWitness> entry in properties.entries) {
Key key = entry.key;
PropertyWitness witness = entry.value;
if (forCorrection && witness.isTrivial) {
continue;
}
if (key is ExtensionKey) {
(witnessFieldsByType[key.receiverType] ??= {})[key] = witness;
} else {
(witnessFieldsByType[valueType] ??= {})[key] = witness;
}
}
if (witnessFieldsByType.isNotEmpty) {
String and = '';
for (MapEntry<StaticType, Map<Key, PropertyWitness>> entry
in witnessFieldsByType.entries) {
StaticType type = entry.key;
Map<Key, PropertyWitness> witnessFields = entry.value;
buffer.write(and);
and = ' && ';
type.witnessToDart(buffer, this, witnessFields,
forCorrection: forCorrection);
}
} else {
valueType.witnessToDart(buffer, this, const {},
forCorrection: forCorrection);
}
} else {
valueType.witnessToDart(buffer, this, const {},
forCorrection: forCorrection);
}
}
bool get isTrivial {
if (!staticType.isSubtypeOf(valueType)) return false;
for (PropertyWitness property in properties.values) {
if (!property.isTrivial) {
return false;
}
}
return true;
}
/// Returns the witness as pattern syntax including all subproperties.
String get asWitness {
DartTemplateBuffer buffer = new SimpleDartBuffer();
witnessToDart(buffer, forCorrection: false);
return buffer.toString();
}
/// Return the witness as pattern syntax without subproperties that fully
/// match the static type.
String get asCorrection {
DartTemplateBuffer buffer = new SimpleDartBuffer();
witnessToDart(buffer, forCorrection: true);
return buffer.toString();
}
@override
String toString() => asWitness;
}

View file

@ -1,18 +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 '../messages/codes.dart';
import 'flags.dart';
/// Returns the message used to report that [experimentalFlag] feature is not
/// enabled.
Message getExperimentNotEnabledMessage(ExperimentalFlag experimentalFlag) {
if (experimentalFlag.isEnabledByDefault) {
return templateExperimentNotEnabled.withArguments(experimentalFlag.name,
experimentalFlag.experimentEnabledVersion.toText());
} else {
return templateExperimentNotEnabledOffByDefault
.withArguments(experimentalFlag.name);
}
}

View file

@ -1,311 +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.
// NOTE: THIS FILE IS GENERATED. DO NOT EDIT.
//
// Instead modify 'tools/experimental_features.yaml' and run
// 'dart pkg/front_end/tool/cfe.dart generate-experimental-flags' to update.
const Version defaultLanguageVersion = const Version(3, 9);
/// Enum for experimental flags shared between the CFE and the analyzer.
enum ExperimentalFlag {
augmentations(
name: 'augmentations',
isEnabledByDefault: false,
isExpired: false,
experimentEnabledVersion: defaultLanguageVersion,
experimentReleasedVersion: const Version(3, 6)),
classModifiers(
name: 'class-modifiers',
isEnabledByDefault: true,
isExpired: true,
experimentEnabledVersion: const Version(3, 0),
experimentReleasedVersion: const Version(3, 0)),
constFunctions(
name: 'const-functions',
isEnabledByDefault: false,
isExpired: false,
experimentEnabledVersion: defaultLanguageVersion,
experimentReleasedVersion: defaultLanguageVersion),
constantUpdate2018(
name: 'constant-update-2018',
isEnabledByDefault: true,
isExpired: true,
experimentEnabledVersion: const Version(2, 0),
experimentReleasedVersion: const Version(2, 0)),
constructorTearoffs(
name: 'constructor-tearoffs',
isEnabledByDefault: true,
isExpired: true,
experimentEnabledVersion: const Version(2, 15),
experimentReleasedVersion: const Version(2, 15)),
controlFlowCollections(
name: 'control-flow-collections',
isEnabledByDefault: true,
isExpired: true,
experimentEnabledVersion: const Version(2, 0),
experimentReleasedVersion: const Version(2, 0)),
digitSeparators(
name: 'digit-separators',
isEnabledByDefault: true,
isExpired: true,
experimentEnabledVersion: const Version(3, 6),
experimentReleasedVersion: const Version(3, 6)),
dotShorthands(
name: 'dot-shorthands',
isEnabledByDefault: false,
isExpired: false,
experimentEnabledVersion: defaultLanguageVersion,
experimentReleasedVersion: defaultLanguageVersion),
enhancedEnums(
name: 'enhanced-enums',
isEnabledByDefault: true,
isExpired: true,
experimentEnabledVersion: const Version(2, 17),
experimentReleasedVersion: const Version(2, 17)),
enhancedParts(
name: 'enhanced-parts',
isEnabledByDefault: false,
isExpired: false,
experimentEnabledVersion: defaultLanguageVersion,
experimentReleasedVersion: const Version(3, 6)),
extensionMethods(
name: 'extension-methods',
isEnabledByDefault: true,
isExpired: true,
experimentEnabledVersion: const Version(2, 6),
experimentReleasedVersion: const Version(2, 6)),
genericMetadata(
name: 'generic-metadata',
isEnabledByDefault: true,
isExpired: true,
experimentEnabledVersion: const Version(2, 14),
experimentReleasedVersion: const Version(2, 14)),
getterSetterError(
name: 'getter-setter-error',
isEnabledByDefault: false,
isExpired: false,
experimentEnabledVersion: defaultLanguageVersion,
experimentReleasedVersion: defaultLanguageVersion),
inferenceUpdate1(
name: 'inference-update-1',
isEnabledByDefault: true,
isExpired: true,
experimentEnabledVersion: const Version(2, 18),
experimentReleasedVersion: const Version(2, 18)),
inferenceUpdate2(
name: 'inference-update-2',
isEnabledByDefault: true,
isExpired: true,
experimentEnabledVersion: const Version(3, 2),
experimentReleasedVersion: const Version(3, 2)),
inferenceUpdate3(
name: 'inference-update-3',
isEnabledByDefault: true,
isExpired: true,
experimentEnabledVersion: const Version(3, 4),
experimentReleasedVersion: const Version(3, 4)),
inferenceUpdate4(
name: 'inference-update-4',
isEnabledByDefault: false,
isExpired: false,
experimentEnabledVersion: defaultLanguageVersion,
experimentReleasedVersion: defaultLanguageVersion),
inferenceUsingBounds(
name: 'inference-using-bounds',
isEnabledByDefault: true,
isExpired: true,
experimentEnabledVersion: const Version(3, 7),
experimentReleasedVersion: const Version(3, 7)),
inlineClass(
name: 'inline-class',
isEnabledByDefault: true,
isExpired: true,
experimentEnabledVersion: const Version(3, 3),
experimentReleasedVersion: const Version(3, 3)),
macros(
name: 'macros',
isEnabledByDefault: false,
isExpired: false,
experimentEnabledVersion: defaultLanguageVersion,
experimentReleasedVersion: const Version(3, 3)),
namedArgumentsAnywhere(
name: 'named-arguments-anywhere',
isEnabledByDefault: true,
isExpired: true,
experimentEnabledVersion: const Version(2, 17),
experimentReleasedVersion: const Version(2, 17)),
nativeAssets(
name: 'native-assets',
isEnabledByDefault: false,
isExpired: false,
experimentEnabledVersion: defaultLanguageVersion,
experimentReleasedVersion: defaultLanguageVersion),
nonNullable(
name: 'non-nullable',
isEnabledByDefault: true,
isExpired: true,
experimentEnabledVersion: const Version(2, 12),
experimentReleasedVersion: const Version(2, 10)),
nonfunctionTypeAliases(
name: 'nonfunction-type-aliases',
isEnabledByDefault: true,
isExpired: true,
experimentEnabledVersion: const Version(2, 13),
experimentReleasedVersion: const Version(2, 13)),
nullAwareElements(
name: 'null-aware-elements',
isEnabledByDefault: true,
isExpired: false,
experimentEnabledVersion: const Version(3, 8),
experimentReleasedVersion: const Version(3, 8)),
patterns(
name: 'patterns',
isEnabledByDefault: true,
isExpired: true,
experimentEnabledVersion: const Version(3, 0),
experimentReleasedVersion: const Version(3, 0)),
recordUse(
name: 'record-use',
isEnabledByDefault: false,
isExpired: false,
experimentEnabledVersion: defaultLanguageVersion,
experimentReleasedVersion: defaultLanguageVersion),
records(
name: 'records',
isEnabledByDefault: true,
isExpired: true,
experimentEnabledVersion: const Version(3, 0),
experimentReleasedVersion: const Version(3, 0)),
sealedClass(
name: 'sealed-class',
isEnabledByDefault: true,
isExpired: true,
experimentEnabledVersion: const Version(3, 0),
experimentReleasedVersion: const Version(3, 0)),
setLiterals(
name: 'set-literals',
isEnabledByDefault: true,
isExpired: true,
experimentEnabledVersion: const Version(2, 0),
experimentReleasedVersion: const Version(2, 0)),
soundFlowAnalysis(
name: 'sound-flow-analysis',
isEnabledByDefault: false,
isExpired: false,
experimentEnabledVersion: defaultLanguageVersion,
experimentReleasedVersion: defaultLanguageVersion),
spreadCollections(
name: 'spread-collections',
isEnabledByDefault: true,
isExpired: true,
experimentEnabledVersion: const Version(2, 0),
experimentReleasedVersion: const Version(2, 0)),
superParameters(
name: 'super-parameters',
isEnabledByDefault: true,
isExpired: true,
experimentEnabledVersion: const Version(2, 17),
experimentReleasedVersion: const Version(2, 17)),
testExperiment(
name: 'test-experiment',
isEnabledByDefault: false,
isExpired: false,
experimentEnabledVersion: defaultLanguageVersion,
experimentReleasedVersion: defaultLanguageVersion),
tripleShift(
name: 'triple-shift',
isEnabledByDefault: true,
isExpired: true,
experimentEnabledVersion: const Version(2, 14),
experimentReleasedVersion: const Version(2, 14)),
unnamedLibraries(
name: 'unnamed-libraries',
isEnabledByDefault: true,
isExpired: true,
experimentEnabledVersion: const Version(2, 19),
experimentReleasedVersion: const Version(2, 19)),
unquotedImports(
name: 'unquoted-imports',
isEnabledByDefault: false,
isExpired: false,
experimentEnabledVersion: defaultLanguageVersion,
experimentReleasedVersion: defaultLanguageVersion),
variance(
name: 'variance',
isEnabledByDefault: false,
isExpired: false,
experimentEnabledVersion: defaultLanguageVersion,
experimentReleasedVersion: defaultLanguageVersion),
wildcardVariables(
name: 'wildcard-variables',
isEnabledByDefault: true,
isExpired: true,
experimentEnabledVersion: const Version(3, 7),
experimentReleasedVersion: const Version(3, 7)),
;
final String name;
final bool isEnabledByDefault;
final bool isExpired;
final Version experimentEnabledVersion;
final Version experimentReleasedVersion;
const ExperimentalFlag(
{required this.name,
required this.isEnabledByDefault,
required this.isExpired,
required this.experimentEnabledVersion,
required this.experimentReleasedVersion});
}
class Version {
final int major;
final int minor;
const Version(this.major, this.minor);
String toText() => '$major.$minor';
@override
String toString() => toText();
}

View file

@ -1,431 +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/util/dependency_walker.dart';
import 'flow_analysis/flow_analysis_operations.dart';
/// Information about a [Class] that is necessary for computing the set of
/// private `noSuchMethod` forwarding getters the compiler will generate.
///
/// The type parameter [Class] has the same meaning as in [FieldPromotability].
class ClassInfo<Class extends Object> {
final _InterfaceNode<Class> _interfaceNode;
final _ImplementedNode<Class> _implementedNode;
ClassInfo(this._interfaceNode, this._implementedNode);
Class get _class => _interfaceNode._class;
}
/// Reasons why property accesses having a given field name are non-promotable.
///
/// This class is part of the data structure output by
/// [FieldPromotability.computeNonPromotabilityInfo].
///
/// The type parameters [Class], [Field], and [Getter] have the same meaning as
/// in [FieldPromotability].
class FieldNameNonPromotabilityInfo<Class extends Object, Field, Getter> {
/// The fields with the given name that are not promotable (either because
/// they are not final or because they are external).
///
/// (This list is initially empty and
/// [FieldPromotability.computeNonPromotabilityInfo] accumulates entries into
/// it.)
final List<Field> conflictingFields = [];
/// The explicit concrete getters with the given name.
///
/// (This list is initially empty and
/// [FieldPromotability.computeNonPromotabilityInfo] accumulates entries into
/// it.)
final List<Getter> conflictingGetters = [];
/// The classes that implicitly forward a getter with the given name to
/// `noSuchMethod`.
///
/// The purpose of this list is so that the client can generate error messages
/// that clarify to the user that field promotion was not possible due to
/// `noSuchMethod` forwarding getters. It is a list of [Class] rather than
/// [Getter] because `noSuchMethod` forwarding getters are always implicit;
/// hence the error messages should be associated with the class.
///
/// (This list is initially empty and
/// [FieldPromotability.computeNonPromotabilityInfo] accumulates entries into
/// it.)
final List<Class> conflictingNsmClasses = [];
FieldNameNonPromotabilityInfo._();
}
/// This class examines all the [Class]es in a library and determines which
/// fields are promotable within that library.
///
/// The type parameters [Class], [Field] and [Getter] should be the data
/// structures used by the client to represent classes, fields, and getters in
/// the user's program. This class treats these types abstractly, so that each
/// concrete Dart implementation can use their own representation of the user's
/// code.
///
/// Note: the term [Class] is used in a general sense here; it can represent any
/// class, mixin, or enum declared in the user's program.
///
/// A field is considered promotable if all of the following are true:
/// - It is final
/// - Its name is private
/// - The library doesn't contain any non-final fields, concrete getters, or
/// `noSuchMethod` getters with the same name.
///
/// These rules were chosen because they are sufficient to determine that all
/// reads of the field that complete normally will either throw an exception,
/// return a single stable object, or return objects with a single stable
/// runtime type; this ensures that type promotion is safe.
///
/// The reason the field must be private is because if it isn't, then at
/// runtime, a read of the field might resolve to a getter or a non-final field
/// in a subclass that's implemented in some other library, and hence the result
/// of the read won't be stable.
///
/// Note that as a result of the third bullet item, any private name that's
/// associated with a non-final field, concrete getter, or `noSuchMethod` getter
/// renders all fields with the same name unpromotable within the library. The
/// reason for this is that since most Dart classes can also be used as
/// interfaces (via an `implements` clause), a read that statically resolves to
/// a private final field might not actually resolve to that field at runtime;
/// it might resolve to some other field or getter with the same name. (In
/// principle it would be possible to narrow the set of possible resolution
/// targets through whole-program analysis, or by careful consideration of class
/// modifiers and public vs. private class names; however that would make the
/// analysis much more complex, and also make it difficult to explain to users
/// when to expect field promotion to work; so we've elected to simply assume
/// that all fields and getters with the same name might potentially alias to
/// one another).
///
/// Since all fields with the same name within the library have the same
/// promotability, this class doesn't attempt to assign a promotability boolean
/// to each field; instead it computes the set of private names for which field
/// promotion is not allowed.
///
/// A word about `noSuchMethod` getters: a `noSuchMethod` getter is a
/// `noSuchMethod` forwarder that implements a getter. The compiler generates
/// `noSuchMethod` forwarders when a concrete [Class] inherits a member into its
/// interface (via an `implements` clause), but it doesn't contain or inherit a
/// concrete implementation of that member. (Note that this is only legal if the
/// class contains or inherits an implementation of `noSuchMethod`). If the name
/// of the inherited member is accessible in the [Class]'s library (i.e. it's
/// either a public name or it's a private name that belongs to the [Class]'s
/// library), then the synthetic `noSuchMethod` forwarder calls `noSuchMethod`
/// and passes along the return value (casting it to the proper type). If the
/// name of the inherited member *isn't* accessible in the [Class]'s library,
/// then the synthetic `noSuchMethod` forwarder throws an exception.
///
/// `noSuchMethod` getters that forward to `noSuchMethod` defeat field promotion
/// for the same reason that ordinary getters do (they aren't guaranteed to
/// return a stable value). However, `noSuchMethod` getters that simply throw an
/// exception do not defeat field promotion, because the exception prevents any
/// code that relies on field promotion soundness from being reachable. Since
/// only private fields are eligible from promotion in the first place, this
/// means that it's only necessary to search the current library for
/// `noSuchMethod` getters that might defeat field promotion (because a
/// `noSuchMethod` getter in another library that's associated with a private
/// name in *this* library will always throw).
///
/// Note that it's possible that one class will have a final field with a given
/// private name, while another class will have a method with the same name (or
/// a `noSuchMethod` forwarder for such a method). If this happens, then an
/// attempt to read the field might at runtime resolve to a tear-off of the
/// corresponding method. This is ok (it's not necessary to suppress promotion
/// of the field), because even though the resulting tear-offs might not be
/// stable in the sense of always being identical, they will nonetheless always
/// have the same runtime type. Hence, we can completely ignore methods when
/// computing which fields in the library are promotable.
abstract class FieldPromotability<Class extends Object, Field, Getter> {
/// Map whose keys are the field names in the library that have been
/// determined to be unsafe to promote, and whose values are an instance of
/// `FieldNameNonPromotabilityInfo` describing why.
final Map<String, FieldNameNonPromotabilityInfo<Class, Field, Getter>>
_nonPromotabilityInfo = {};
/// Map from a [Class] object to the [_ImplementedNode] that records the names
/// of concrete fields and getters declared in or inherited by the [Class].
final Map<Class, _ImplementedNode<Class>> _implementedNodes =
new Map.identity();
/// Map from a [Class] object to the [_InterfaceNode] that records the names
/// of getters in the interface for a [Class].
final Map<Class, _InterfaceNode<Class>> _interfaceNodes = new Map.identity();
/// List of information about concrete [Class]es in the library.
final List<ClassInfo<Class>> _concreteInfoList = [];
/// Records the presence of [class_] in the library. The client should call
/// this method once for each class, mixin, and enum declared in the library.
///
/// Returns a [ClassInfo] object describing the class. After calling this
/// method, the client should call [addField] and [addGetter] to record all
/// the non-synthetic instance fields and getters in the class.
ClassInfo<Class> addClass(Class class_, {required bool isAbstract}) {
ClassInfo<Class> classInfo = new ClassInfo<Class>(
_getInterfaceNode(class_), _getImplementedNode(class_));
if (!isAbstract) {
_concreteInfoList.add(classInfo);
}
return classInfo;
}
/// Records that the [Class] described by [classInfo] contains non-synthetic
/// instance [field] with the given [name].
///
/// [isFinal] indicates whether the field is a final field. [isAbstract]
/// indicates whether the field is abstract. [isExternal] indicates whether
/// the field is external.
///
/// A return value of `null` indicates that this field *might* wind up being
/// promotable; any other return value indicates the reason why it
/// *definitely* isn't promotable.
PropertyNonPromotabilityReason? addField(
ClassInfo<Class> classInfo, Field field, String name,
{required bool isFinal,
required bool isAbstract,
required bool isExternal}) {
// Public fields are never promotable, so we may safely ignore fields with
// public names.
if (!name.startsWith('_')) {
return PropertyNonPromotabilityReason.isNotPrivate;
}
// Record the field name for later use in computation of `noSuchMethod`
// getters.
classInfo._interfaceNode._directNames.add(name);
if (!isAbstract) {
classInfo._implementedNode._directNames.add(name);
}
if (isExternal || !isFinal) {
// The field isn't promotable, nor is any other field in the library with
// the same name.
_fieldNonPromoInfo(name).conflictingFields.add(field);
return isExternal
? PropertyNonPromotabilityReason.isExternal
: PropertyNonPromotabilityReason.isNotFinal;
}
// The field is final and not external, so it might wind up being
// promotable.
return null;
}
/// Records that the [Class] described by [classInfo] contains a non-synthetic
/// instance [getter] with the given [name].
///
/// [isAbstract] indicates whether the getter is abstract.
///
/// A return value of `null` indicates that this getter *might* wind up being
/// promotable; any other return value indicates the reason why it
/// *definitely* isn't promotable.
PropertyNonPromotabilityReason? addGetter(
ClassInfo<Class> classInfo, Getter getter, String name,
{required bool isAbstract}) {
// Public fields are never promotable, so we may safely ignore getters with
// public names.
if (!name.startsWith('_')) {
return PropertyNonPromotabilityReason.isNotPrivate;
}
// Record the getter name for later use in computation of `noSuchMethod`
// getters.
classInfo._interfaceNode._directNames.add(name);
if (!isAbstract) {
classInfo._implementedNode._directNames.add(name);
// The getter is concrete, so no fields with the same name are promotable.
_fieldNonPromoInfo(name).conflictingGetters.add(getter);
return PropertyNonPromotabilityReason.isNotField;
} else {
return null;
}
}
/// Computes the set of private field names which are not safe to promote in
/// the library, along with the reasons why.
///
/// The client should call this method once after all [Class]es, fields, and
/// getters have been recorded using [addClass], [addField], and [addGetter].
Map<String, FieldNameNonPromotabilityInfo<Class, Field, Getter>>
computeNonPromotabilityInfo() {
// The names of private non-final fields and private getters have already
// been added to [_unpromotableFieldNames] by [addField] and [addGetter]. So
// all that remains to do is figure out which field names are unpromotable
// due to the presence of `noSuchMethod` getters. To do this, we'll need to
// walk the class hierarchy and gather (a) the names of private instance
// getters in each class's interface, and (b) the names of private instance
// fields and getters in each class's implementation.
_ClassHierarchyWalker<Class> interfaceWalker =
new _ClassHierarchyWalker<Class>();
_ClassHierarchyWalker<Class> implementedWalker =
new _ClassHierarchyWalker<Class>();
// For each concrete class in the library,
for (ClassInfo<Class> info in _concreteInfoList) {
// Compute names of getters in the interface.
_InterfaceNode<Class> interfaceNode = info._interfaceNode;
interfaceWalker.walk(interfaceNode);
Set<String> interfaceNames = interfaceNode._transitiveNames!;
// Compute names of actually implemented getters.
_ImplementedNode<Class> implementedNode = info._implementedNode;
implementedWalker.walk(implementedNode);
Set<String> implementedNames = implementedNode._transitiveNames!;
// `noSuchMethod`-forwarding getters will be generated for getters that
// are in the interface, but not actually implemented; consequently,
// fields with these names are not safe to promote.
for (String name in interfaceNames) {
if (!implementedNames.contains(name)) {
_fieldNonPromoInfo(name).conflictingNsmClasses.add(info._class);
}
}
}
return _nonPromotabilityInfo;
}
/// Returns an iterable of the direct superclasses of [class_]. If
/// [ignoreImplements] is `true`, then superclasses reached through an
/// `implements` clause are ignored.
///
/// This is used to gather the transitive closure of fields and getters
/// present in a class's interface and implementation. Therefore, it does not
/// matter whether the client uses a fully sugared model of mixins (in which
/// `class C extends B with M1, M2 {}` is represented as a single class with
/// direct superclasses `B`, `M1`, and `M2`) or a fully desugared model (in
/// which `class C extends B with M1, M2` represents a class `C` with
/// superclass `B&M1&M2`, which in turn has supertypes `B&M1` and `M2`, etc.)
Iterable<Class> getSuperclasses(Class class_,
{required bool ignoreImplements});
/// Gets the [FieldNameNonPromotabilityInfo] object corresponding to [name]
/// from [_nonPromotabilityInfo], creating it if necessary.
FieldNameNonPromotabilityInfo<Class, Field, Getter> _fieldNonPromoInfo(
String name) =>
_nonPromotabilityInfo.putIfAbsent(name, FieldNameNonPromotabilityInfo._);
/// Gets or creates the [_ImplementedNode] for [class_].
_ImplementedNode<Class> _getImplementedNode(Class class_) =>
_implementedNodes[class_] ??= new _ImplementedNode<Class>(this, class_);
/// Gets or creates the [_InterfaceNode] for [class_].
_InterfaceNode<Class> _getInterfaceNode(Class class_) =>
_interfaceNodes[class_] ??= new _InterfaceNode<Class>(this, class_);
}
/// Dependency walker that traverses the graph of a class's type hierarchy,
/// gathering the transitive closure of field and getter names.
///
/// This is based on the [DependencyWalker] class, which implements Tarjan's
/// strongly connected component's algorithm in order to efficiently handle
/// cycles in the class hierarchy (which the analyzer tolerates).
class _ClassHierarchyWalker<Class extends Object>
extends DependencyWalker<_Node<Class>> {
@override
void evaluate(_Node<Class> v) => evaluateScc([v]);
@override
void evaluateScc(List<_Node<Class>> scc) {
// Gather the names directly declared in all the classes in this strongly
// connected component, plus all the names in the transitive closure of the
// strongly connected components this component depends on.
Set<String> transitiveNames = <String>{};
for (_Node<Class> node in scc) {
transitiveNames.addAll(node._directNames);
for (_Node<Class> dependency in Node.getDependencies(node)) {
Set<String>? namesFromDependency = dependency._transitiveNames;
if (namesFromDependency != null) {
transitiveNames.addAll(namesFromDependency);
}
}
}
// Store this list of names in all the nodes of this strongly connected
// component.
for (_Node<Class> node in scc) {
node._transitiveNames = transitiveNames;
}
}
}
/// Data structure tracking the set of private fields and getters a [Class]
/// concretely implements.
///
/// This data structure extends [_Node] so that we can efficiently walk the
/// superclass chain (without having to worry about circularities) in order to
/// include getters concretely implemented in superclasses and mixins.
class _ImplementedNode<Class extends Object> extends _Node<Class> {
_ImplementedNode(super.fieldPromotability, super.element);
@override
List<_Node<Class>> computeDependencies() {
// We need to gather field and getter names from the transitive closure of
// superclasses, following `with` and `extends` edges but not `implements`
// edges. So the set of dependencies of this node is the set of immediate
// superclasses, ignoring `implements`.
List<_Node<Class>> dependencies = [];
for (Class supertype in _fieldPromotability.getSuperclasses(_class,
ignoreImplements: true)) {
dependencies.add(_fieldPromotability._getImplementedNode(supertype));
}
return dependencies;
}
}
/// Data structure tracking the set of getters in a [Class]'s interface.
///
/// This data structure extends [_Node] so that we can efficiently walk the
/// class hierarchy (without having to worry about circularities) in order to
/// include getters defined in superclasses, mixins, and interfaces.
class _InterfaceNode<Class extends Object> extends _Node<Class> {
_InterfaceNode(super.fieldPromotability, super.element);
@override
List<_Node<Class>> computeDependencies() {
// We need to gather field and getter names from the transitive closure of
// superclasses, following `with`, `extends`, `implements`, and `on` edges.
// So the set of dependencies of this node is the set of immediate
// superclasses, including `implements`.
List<_Node<Class>> dependencies = [];
for (Class supertype in _fieldPromotability.getSuperclasses(_class,
ignoreImplements: false)) {
dependencies.add(_fieldPromotability._getInterfaceNode(supertype));
}
return dependencies;
}
}
/// A node in either the graph of a class's type hierarchy, recording the
/// information necessary for computing the set of private `noSuchMethod`
/// getters the compiler will generate.
abstract class _Node<Class extends Object> extends Node<_Node<Class>> {
/// A reference back to the [FieldPromotability] object.
final FieldPromotability<Class, Object?, Object?> _fieldPromotability;
/// The [Class] represented by this node.
final Class _class;
/// The names of getters declared by [_class] directly.
final Set<String> _directNames = {};
/// The names of getters declared by [_class] and its superinterfaces.
///
/// Populated when [_ClassHierarchyWalker] encounters a strongly connected
/// component.
Set<String>? _transitiveNames;
_Node(this._fieldPromotability, this._class);
@override
bool get isEvaluated => _transitiveNames != null;
}

View file

@ -1,91 +0,0 @@
// Copyright (c) 2020, 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.
mixin FactorTypeTestMixin<Type> {
Type futureNone(Type type);
Type futureOrNone(Type type);
Type get dynamicType;
Type get intNone;
Type get intQuestion;
Type get numNone;
Type get numQuestion;
Type get objectNone;
Type get objectQuestion;
Type get stringNone;
Type get stringQuestion;
Type get nullNone;
Type get voidType;
void test_dynamic() {
check(dynamicType, intNone, 'dynamic');
}
void test_futureOr() {
check(futureOrNone(intNone), intNone, 'Future<int>');
check(futureOrNone(intNone), futureNone(intNone), 'int');
check(futureOrNone(intQuestion), intNone, 'FutureOr<int?>');
check(futureOrNone(intQuestion), futureNone(intNone), 'FutureOr<int?>');
check(futureOrNone(intQuestion), intQuestion, 'Future<int?>');
check(futureOrNone(intQuestion), futureNone(intQuestion), 'int?');
check(futureOrNone(intNone), numNone, 'Future<int>');
check(futureOrNone(intNone), futureNone(numNone), 'int');
}
void test_object() {
check(objectNone, objectNone, 'Never');
check(objectNone, objectQuestion, 'Never');
check(objectNone, intNone, 'Object');
check(objectNone, intQuestion, 'Object');
check(objectQuestion, objectNone, 'Never?');
check(objectQuestion, objectQuestion, 'Never');
check(objectQuestion, intNone, 'Object?');
check(objectQuestion, intQuestion, 'Object');
}
test_subtype() {
check(intNone, intNone, 'Never');
check(intNone, intQuestion, 'Never');
check(intQuestion, intNone, 'Never?');
check(intQuestion, intQuestion, 'Never');
check(intNone, numNone, 'Never');
check(intNone, numQuestion, 'Never');
check(intQuestion, numNone, 'Never?');
check(intQuestion, numQuestion, 'Never');
check(intNone, nullNone, 'int');
check(intQuestion, nullNone, 'int');
check(intNone, stringNone, 'int');
check(intQuestion, stringNone, 'int?');
check(intNone, stringQuestion, 'int');
check(intQuestion, stringQuestion, 'int');
}
void test_void() {
check(voidType, intNone, 'void');
}
Type factor(Type T, Type S);
void expect(Type T, Type S, String actualResult, String expectedResult);
void check(Type T, Type S, String expectedStr) {
Type result = factor(T, S);
String resultStr = typeString(result);
expect(T, S, resultStr, expectedStr);
}
String typeString(Type type);
}

View file

@ -1,145 +0,0 @@
// 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.
/// @docImport 'flow_analysis.dart';
/// @docImport '../field_promotability.dart';
/// @docImport '../type_inference/type_analyzer_operations.dart';
library;
/// Callback API used by flow analysis to query and manipulate the client's
/// representation of variables and types.
abstract interface class FlowAnalysisOperations<Variable extends Object,
Type extends Object> implements FlowAnalysisTypeOperations<Type> {
/// Whether the given [variable] was declared with the `final` modifier.
bool isFinal(Variable variable);
/// Determines whether the given property can be promoted.
///
/// [property] will correspond to a `propertyMember` value passed to
/// [FlowAnalysis.promotedPropertyType], [FlowAnalysis.propertyGet], or
/// [FlowAnalysis.pushPropertySubpattern].
///
/// This method will not be called if field promotion is disabled for the
/// current library.
bool isPropertyPromotable(Object property);
/// Returns the static type of the given [variable].
Type variableType(Variable variable);
/// Returns additional information about why a given property couldn't be
/// promoted. [property] will correspond to a `propertyMember` value passed to
/// [FlowAnalysis.promotedPropertyType], [FlowAnalysis.propertyGet], or
/// [FlowAnalysis.pushPropertySubpattern].
///
/// This method is only called if a closure returned by
/// [FlowAnalysis.whyNotPromoted] is invoked, and the expression being queried
/// is a reference to a private property that wasn't promoted; this typically
/// means that an error occurred and the client is attempting to produce a
/// context message to provide additional information about the error (i.e.,
/// that the error happened due to failed promotion).
///
/// The client should return `null` if [property] was not promotable due to a
/// conflict with a field, getter, or noSuchMethod forwarder elsewhere in the
/// library; if this happens, the closure returned by
/// [FlowAnalysis.whyNotPromoted] will yield an object of type
/// [PropertyNotPromotedForNonInherentReason] containing enough information
/// for the client to be able to generate the appropriate context information.
///
/// If this method is called when analyzing a library for which field
/// promotion is disabled, and the property in question *would* have been
/// promotable if field promotion had been enabled, the client should return
/// `null`; otherwise it should behave as if field promotion were enabled.
PropertyNonPromotabilityReason? whyPropertyIsNotPromotable(Object property);
}
/// Callback API used by flow analysis to query and manipulate the client's
/// representation of types.
abstract interface class FlowAnalysisTypeOperations<Type extends Object> {
/// Returns the client's representation of the type `bool`.
Type get boolType;
/// Classifies the given type into one of the three categories defined by
/// the [TypeClassification] enum.
TypeClassification classifyType(Type type);
/// If [type] is an extension type, returns the ultimate representation type.
/// Otherwise returns [type] as is.
Type extensionTypeErasure(Type type);
/// Returns the "remainder" of [from] when [what] has been removed from
/// consideration by an instance check.
Type factor(Type from, Type what);
/// Determines whether the given [type] is a bottom type.
///
/// A type is a bottom type if it:
/// (a) is the `Never` type itself.
/// (b) is a type variable that extends `Never`, OR
/// (c) is a type variable that has been promoted to `Never`
bool isBottomType(Type type);
/// Return `true` if the [leftType] is a subtype of the [rightType].
bool isSubtypeOf(Type leftType, Type rightType);
/// Returns `true` if [type] is a reference to a type parameter.
bool isTypeParameterType(Type type);
/// Computes the nullable form of [type], in other words the least upper bound
/// of [type] and `Null`.
///
/// The concrete classes implementing [TypeAnalyzerOperations] should mix in
/// [TypeAnalyzerOperationsMixin] and implement
/// [TypeAnalyzerOperations.makeNullableInternal] to receive a concrete
/// implementation of [makeNullable] instead of implementing [makeNullable]
/// directly.
Type makeNullable(Type type);
/// Returns the non-null promoted version of [type].
///
/// Note that some types don't have a non-nullable version (e.g.
/// `FutureOr<int?>`), so [type] may be returned even if it is nullable.
Type promoteToNonNull(Type type);
/// Tries to promote to the first type from the second type, and returns the
/// promoted type if it succeeds, otherwise null.
Type? tryPromoteToType(Type to, Type from);
}
/// Possible reasons why a property may not be promotable.
///
/// This enum captures the possible non-promotability reasons that are inherent
/// to the property declaration itself. A property may also be non-promotable
/// because field promotion is disabled, or due to a conflict with another
/// declaration; the code that handles those two reasons doesn't use this enum.
///
/// Some of these reasons are distinguished by [FieldPromotability.addField];
/// others must be distinguished by the client.
enum PropertyNonPromotabilityReason {
/// The property is not promotable because it's not a field (it's either a
/// getter or a tear-off of a method).
isNotField,
/// The property is not promotable because its name is public.
isNotPrivate,
/// The property is not promotable because it's an external field.
isExternal,
/// The property is not promotable because it's a non-final field.
isNotFinal,
}
/// Enum representing the different classifications of types that can be
/// returned by [FlowAnalysisTypeOperations.classifyType].
enum TypeClassification {
/// The type is `Null` or an equivalent type (e.g. `Never?`)
nullOrEquivalent,
/// The type is a potentially nullable type, but not equivalent to `Null`
/// (e.g. `int?`, or a type variable whose bound is potentially nullable)
potentiallyNullable,
/// The type is a non-nullable type.
nonNullable,
}

View file

@ -1,351 +0,0 @@
// 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.
/// Abstract base class forming the basis for an efficient immutable map data
/// structure that can track program state.
///
/// Each instance of [FlowLink] represents a key/value pair, where the [key] is
/// a non-negative integer, and the value is stored in the derived class; a map
/// is formed by chaining together multiple [FlowLink] objects through
/// [previous] pointers. In this way, a collection of [FlowLink] objects be used
/// to model program state, with each [FlowLink] representing a change to a
/// single state variable, and the [previous] pointer pointing to the previous
/// state of the program. In this interpretation, `null` represents the initial
/// program state, in which all state variables take on their default values.
///
/// Multiple [FlowLink] objects are allowed to point to the same [previous]
/// object; in this way, all [FlowLink] objects implicitly form a tree, with
/// `null` at the root. In the interpretation where a collection of [FlowLink]
/// objects are used to model program state, and a single [FlowLink] represents
/// a change to a single state variable, the tree corresponds to the dominator
/// tree. There are no "child" pointers, so the tree may only be traversed in
/// the leaf-to-root direction, and once a branch is no longer needed it will be
/// reclaimed by the garbage collector.
///
/// The [FlowLinkReader] class may be used to efficiently look up map entries in
/// a given [FlowLink] object. It makes use of the fact that [key]s are
/// non-negative integers to maintain a current state in a list.
///
/// The generic parameter [Link] should be instantiated with the derived class.
abstract base class FlowLink<Link extends FlowLink<Link>> {
/// The integer key for this [FlowLink]. In the interpretation where a
/// collection of [FlowLink] objects are used to model program state, and a
/// single [FlowLink] represents a change to a single state variable, this key
/// tells which state variable has changed.
final int key;
/// Pointer allowing multiple [FlowLink] objects to be joined into a singly
/// linked list. In the interpretation where a collection of [FlowLink]
/// objects are used to model program state, and a single [FlowLink]
/// represents a change to a single state variable, this pointer points to the
/// state of the program prior to the change.
final Link? previous;
/// Pointer to the nearest [FlowLink] in the [previous] chain whose [key]
/// matches this one, or `null` if there is no matching [FlowLink]. This is
/// used by [FlowLinkReader] to quickly update its state representation when
/// traversing the implicit tree of [FlowLink] objects.
final Link? previousForKey;
/// The number of [previous] links that need to be traversed to reach `null`.
/// This is used by [FlowLinkReader] to quickly find the common ancestor of
/// two points in the implicit tree of [FlowLink] objects.
final int _depth;
/// Creates a new [FlowLink] object. Caller is required to satisfy the
/// invariant described in [previousForKey].
FlowLink(
{required this.key, required this.previous, required this.previousForKey})
: _depth = previous.depth + 1 {
assert(key >= 0);
assert(identical(previousForKey, _computePreviousForKey(key)));
}
/// Debug only: computes the correct value for [previousForKey], to check that
/// the caller supplied the appropriate value to the constructor.
Link? _computePreviousForKey(int key) {
Link? link = previous;
while (link != null) {
if (link.key == key) break;
link = link.previous;
}
return link;
}
}
/// Information about a difference between two program states, returned by
/// [FlowLinkReader.diff].
class FlowLinkDiffEntry<Link extends FlowLink<Link>> {
/// The key that differs between the [FlowLink] maps passed to
/// [FlowLinkReader.diff].
final int key;
/// During a diff operation, the first [FlowLink] associated with [key] that
/// was found while walking the [FlowLink.previous] chain for the `left`
/// argument to [FlowLinkReader.diff], or `null` if no such key has been found
/// yet.
Link? _firstLeft;
/// During a diff operation, the first [FlowLink] associated with [key] that
/// was found while walking the [FlowLink.previous] chain for the `right`
/// argument to [FlowLinkReader.diff], or `null` if no such key has been found
/// yet.
Link? _firstRight;
/// During a diff operation, the value of [FlowLink.previousForKey] that was
/// most recently encountered while walking the [FlowLink.previous] chains for
/// both the `left` and `right` arguments to [FlowLinkReader.diff].
Link? _previousForKey;
FlowLinkDiffEntry._(
{required this.key,
required Link? firstLeft,
required Link? firstRight,
required Link? previousForKey})
: _firstLeft = firstLeft,
_firstRight = firstRight,
_previousForKey = previousForKey;
/// The [FlowLink] associated with [key] in the common ancestor of the two
/// [FlowLink] maps passed to [FlowLinkReader.diff], or `null` if the common
/// ancestor doesn't associate any [FlowLink] with [key].
Link? get ancestor {
// This is called by a client after the `diff` operation has completed.
// Therefore, `_previousForKey` comes from the `FlowLink` that the `diff`
// operation visited last; i.e. the one closest to the common ancestor node.
// So it *is* the common ancestor for the given key.
return _previousForKey;
}
/// The [FlowLink] associated with [key] in the `left` [FlowLink] map passed
/// to [FlowLinkReader.diff], or `null` if the `left` [FlowLink] map doesn't
/// associate any [FlowLink] with [key].
Link? get left {
// This is called by a client after the `diff` operation has completed.
// Therefore, `_firstLeft` is either the first [FlowLink] encountered while
// traversing the linked list for the `left` side of the diff, or it's
// `null` and *no* [FlowLink] was encountered on the left side of the diff
// with the given key; in the latter situation, we may safely return
// `_previousForKey`, which is the common ancestor for the key.
return _firstLeft ?? _previousForKey;
}
/// The [FlowLink] associated with [key] in the `right` [FlowLink] map passed
/// to [FlowLinkReader.diff], or `null` if the `right` [FlowLink] map doesn't
/// associate any [FlowLink] with [key].
Link? get right {
// This is called by a client after the `diff` operation has completed.
// Therefore, `_firstRight` is either the first [FlowLink] encountered while
// traversing the linked list for the `right` side of the diff, or it's
// `null` and *no* [FlowLink] was encountered on the right side of the diff
// with the given key; in the latter situation, we may safely return
// `_previousForKey`, which is the common ancestor for the key.
return _firstRight ?? _previousForKey;
}
}
/// Efficient mechanism for looking up entries in the map formed implicitly by
/// a linked list of [FlowLink] objects, and for finding the difference between
/// two such maps.
///
/// This class works by maintaining a "current" pointer recording the [FlowLink]
/// that was most recently passed to [get], and a cache of the state of all
/// state variables implied by that [FlowLink] object. The cache can be updated
/// in O(n) time, where n is the number of tree edges between one state and
/// another. Accordingly, for maximum efficiency, the caller should try not to
/// jump around the tree too much in successive calls to [get].
class FlowLinkReader<Link extends FlowLink<Link>> {
/// The [FlowLink] pointer most recently passed to [_setCurrent].
Link? _current;
/// A cache of the lookup results that should be returned by [get] for each
/// possible integer key, for the [_current] link.
List<Link?> _cache = [];
/// Temporary scratch area used by [_diffCore]. Each non-null entry represents
/// an index into the list of entries that [_diffCore] will return; that entry
/// has the same integer key as the corresponding index into this list.
List<int?> _diffIndices = [];
/// Computes the difference between [FlowLink] states represented by [left]
/// and [right].
///
/// Two values are returned: the common ancestor of [left] and [right], and
/// a list of [FlowLinkDiffEntry] objects representing the difference among
/// [left], [right], and their common ancestor.
///
/// If [left] and [right] are identical, this method has time complexity
/// `O(1)`.
///
/// Otherwise, this method has time complexity `O(n)`, where `n` is the number
/// of edges between [left] and [right] in the implicit [FlowLink] tree.
({Link? ancestor, List<FlowLinkDiffEntry<Link>> entries}) diff(
Link? left, Link? right) {
if (identical(left, right)) {
return (ancestor: left, entries: const []);
}
List<FlowLinkDiffEntry<Link>> entries = [];
Link? ancestor = _diffCore(left, right, entries);
return (ancestor: ancestor, entries: entries);
}
/// Looks up the first entry in the linked list formed by [FlowLink.previous],
/// starting at [link], whose key matches [key]. If there is no such entry,
/// `null` is returned.
///
/// If [link] is `null` or matches [_current], this method has time
/// complexity `O(1). In this circumstance, [_current] is unchanged.
///
/// Otherwise, this method has time complexity `O(n)`, where `n` is the number
/// of edges between [_current] and [link] in the implicit [FlowLink] tree. In
/// this circumstance, [_current] is set to [link].
Link? get(Link? link, int key) {
if (link == null) {
return null;
}
_setCurrent(link);
return _cache.get(key);
}
/// The core algorithm used by [diff] and [_setCurrent]. Computes a difference
/// between [left] and [right], adding diff entries to [entries]. The return
/// value is the common ancestor of [left] and [right].
Link? _diffCore(
Link? left, Link? right, List<FlowLinkDiffEntry<Link>> entries) {
// The core strategy is to traverse the implicit [FlowLink] tree, starting
// at `left` and `right`, and taking single steps through the linked list
// formed by `FlowLink.previous`, until a common ancestor is found. For each
// step, an entry is added to the `entries` list for the corresponding key
// (or a previously made entry is updated), and `_diffIndices` is modified
// to keep track of which keys have corresponding entries.
// Takes a single step from `left` through the linked list formed by
// `FlowLink.previous`, updating `entries` and `_diffIndices` as
// appropriate.
Link? stepLeft(Link left) {
int key = left.key;
int? index = _diffIndices.get(key);
if (index == null) {
// No diff entry has been created for this key yet, so create one.
_diffIndices.set(key, entries.length);
entries.add(new FlowLinkDiffEntry<Link>._(
key: key,
firstLeft: left,
firstRight: null,
previousForKey: left.previousForKey));
} else {
// A diff entry for this key has already been created, so update it.
entries[index]._firstLeft ??= left;
entries[index]._previousForKey = left.previousForKey;
}
return left.previous;
}
// Takes a single step from `right` through the linked list formed by
// `FlowLink.previous`, updating `entries` and `_diffIndices` as
// appropriate.
Link? stepRight(Link right) {
int key = right.key;
int? index = _diffIndices.get(key);
if (index == null) {
_diffIndices.set(key, entries.length);
entries.add(new FlowLinkDiffEntry<Link>._(
key: key,
firstLeft: null,
firstRight: right,
previousForKey: right.previousForKey));
} else {
entries[index]._firstRight ??= right;
entries[index]._previousForKey = right.previousForKey;
}
return right.previous;
}
// Walk `left` and `right` back to their common ancestor.
int leftDepth = left.depth;
int rightDepth = right.depth;
try {
if (leftDepth > rightDepth) {
do {
// `left.depth > right.depth`, therefore `left.depth > 0`, so
// `left != null`.
left = stepLeft(left!);
leftDepth--;
assert(leftDepth == left.depth);
} while (leftDepth > rightDepth);
} else {
while (rightDepth > leftDepth) {
// `right.depth > left.depth`, therefore `right.depth > 0`, so
// `right != null`.
right = stepRight(right!);
rightDepth--;
assert(rightDepth == right.depth);
}
}
while (!identical(left, right)) {
assert(left.depth == right.depth);
// The only possible value of type `FlowLink?` with a depth of `0` is
// `null`. Therefore, since `left.depth == right.depth`, `left`
// and `right` must either be both `null` or both non-`null`. Since
// they're not identical to one another, it follows that they're both
// non-`null`.
left = stepLeft(left!);
right = stepRight(right!);
}
} finally {
// Clear `_diffIndices` for the next call to this method. Note that we
// don't really expect an exception to occur above, but if one were to
// occur, and we left non-null data in `_diffIndices`, that would produce
// very confusing behavior on future invocations of this method. So to be
// on the safe side, we do this clean up logic is in a `finally`
// clause.
for (int i = 0; i < entries.length; i++) {
FlowLinkDiffEntry<Link> entry = entries[i];
// Since `_diffIndices` was constructed as an index into `entries`, we
// know that `_diffIndices[entry.key] == i`.
assert(_diffIndices[entry.key] == i);
// Therefore, there's no need to use `_diffIndices.set`, since we
// already know that `entry.key < _diffIndices.length`.
_diffIndices[entry.key] = null;
}
}
return left;
}
/// Sets [_current] to [value], updating [_cache] in the process.
void _setCurrent(Link? value) {
if (identical(value, _current)) return;
List<FlowLinkDiffEntry<Link>> entries = [];
_diffCore(_current, value, entries);
for (FlowLinkDiffEntry<Link> entry in entries) {
_cache.set(entry.key, entry.right);
}
_current = value;
}
}
extension on FlowLink<dynamic>? {
/// Gets the `_depth` of `this`, or `0` if `this` is `null`.
int get depth {
FlowLink<dynamic>? self = this;
if (self == null) return 0;
return self._depth;
}
}
extension<T extends Object> on List<T?> {
/// Looks up the `index`th entry in `this` in a safe way, returning `null` if
/// `index` is out of range.
T? get(int index) => index < length ? this[index] : null;
/// Stores `value` in the `index`th entry of `this`, increasing the length of
/// `this` if necessary.
void set(int index, T? value) {
while (index >= length) {
add(null);
}
this[index] = value;
}
}

View file

@ -1,400 +0,0 @@
// Copyright (c) 2017, 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.
library _fe_analyzer_shared.messages.codes;
import 'dart:convert' show JsonEncoder, json;
import 'diagnostic_message.dart' show DiagnosticMessage;
import '../scanner/token.dart' show Token;
import 'severity.dart' show Severity;
import '../util/relativize.dart' as util show isWindows, relativizeUri;
part 'codes_generated.dart';
const int noLength = 1;
class Code<T> {
final String name;
/// The unique positive integer associated with this code,
/// or `-1` if none. This index is used when translating
/// this error to its corresponding Analyzer error.
final int index;
final List<String>? analyzerCodes;
final Severity severity;
const Code(this.name,
{this.index = -1, this.analyzerCodes, this.severity = Severity.error});
@override
String toString() => name;
}
class Message {
final Code<dynamic> code;
final String problemMessage;
final String? correctionMessage;
final Map<String, dynamic> arguments;
const Message(this.code,
{this.correctionMessage,
required this.problemMessage,
this.arguments = const {}});
LocatedMessage withLocation(Uri uri, int charOffset, int length) {
return new LocatedMessage(uri, charOffset, length, this);
}
LocatedMessage withoutLocation() {
return new LocatedMessage(null, -1, noLength, this);
}
@override
String toString() {
return "Message[$code, $problemMessage, $correctionMessage, $arguments]";
}
}
class MessageCode extends Code<Null> implements Message {
@override
final String problemMessage;
@override
final String? correctionMessage;
const MessageCode(super.name,
{super.index,
super.analyzerCodes,
super.severity,
required this.problemMessage,
this.correctionMessage});
@override
Map<String, dynamic> get arguments => const <String, dynamic>{};
@override
Code<dynamic> get code => this;
@override
LocatedMessage withLocation(Uri uri, int charOffset, int length) {
return new LocatedMessage(uri, charOffset, length, this);
}
@override
LocatedMessage withoutLocation() {
return new LocatedMessage(null, -1, noLength, this);
}
}
class Template<T> {
final String messageCode;
final String problemMessageTemplate;
final String? correctionMessageTemplate;
final T withArguments;
const Template(this.messageCode,
{this.correctionMessageTemplate,
required this.problemMessageTemplate,
required this.withArguments});
@override
String toString() => 'Template($messageCode)';
}
class LocatedMessage implements Comparable<LocatedMessage> {
final Uri? uri;
final int charOffset;
final int length;
final Message messageObject;
const LocatedMessage(
this.uri, this.charOffset, this.length, this.messageObject);
Code<dynamic> get code => messageObject.code;
String get problemMessage => messageObject.problemMessage;
String? get correctionMessage => messageObject.correctionMessage;
Map<String, dynamic> get arguments => messageObject.arguments;
@override
int compareTo(LocatedMessage other) {
int result = "${uri}".compareTo("${other.uri}");
if (result != 0) return result;
result = charOffset.compareTo(other.charOffset);
if (result != 0) return result;
return problemMessage.compareTo(problemMessage);
}
FormattedMessage withFormatting(PlainAndColorizedString formatted, int line,
int column, Severity severity, List<FormattedMessage>? relatedInformation,
{List<Uri>? involvedFiles}) {
return new FormattedMessage(this, formatted.plain, formatted.colorized,
line, column, severity, relatedInformation,
involvedFiles: involvedFiles);
}
@override
int get hashCode =>
13 * uri.hashCode +
17 * charOffset.hashCode +
19 * length.hashCode +
23 * messageObject.hashCode;
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is LocatedMessage &&
uri == other.uri &&
charOffset == other.charOffset &&
length == other.length &&
messageObject == other.messageObject;
}
@override
String toString() =>
'LocatedMessage(uri=$uri,charOffset=$charOffset,length=$length,'
'messageObject=$messageObject)';
}
class PlainAndColorizedString {
final String plain;
final String colorized;
@override
String toString() {
assert(false, "Called PlainAndColorizedString.toString: $plain");
return 'PlainAndColorizedString:$plain';
}
const PlainAndColorizedString(this.plain, this.colorized);
const PlainAndColorizedString.plainOnly(this.plain) : this.colorized = plain;
}
class FormattedMessage implements DiagnosticMessage {
final LocatedMessage locatedMessage;
final String formattedPlain;
final String formattedColorized;
final int line;
final int column;
@override
final Severity severity;
final List<FormattedMessage>? relatedInformation;
@override
final List<Uri>? involvedFiles;
const FormattedMessage(
this.locatedMessage,
this.formattedPlain,
this.formattedColorized,
this.line,
this.column,
this.severity,
this.relatedInformation,
{this.involvedFiles});
Code<dynamic> get code => locatedMessage.code;
@override
String get codeName => code.name;
String get problemMessage => locatedMessage.problemMessage;
String? get correctionMessage => locatedMessage.correctionMessage;
Map<String, dynamic> get arguments => locatedMessage.arguments;
Uri? get uri => locatedMessage.uri;
int get charOffset => locatedMessage.charOffset;
int get length => locatedMessage.length;
@override
Iterable<String> get ansiFormatted sync* {
yield formattedColorized;
if (relatedInformation != null) {
for (FormattedMessage m in relatedInformation!) {
yield m.formattedColorized;
}
}
}
@override
Iterable<String> get plainTextFormatted sync* {
yield formattedPlain;
if (relatedInformation != null) {
for (FormattedMessage m in relatedInformation!) {
yield m.formattedPlain;
}
}
}
Map<String, Object?> toJson() {
// This should be kept in sync with package:kernel/problems.md
return <String, Object?>{
"ansiFormatted": ansiFormatted.toList(),
"plainTextFormatted": plainTextFormatted.toList(),
"severity": severity.index,
"uri": uri?.toString(),
"involvedFiles": involvedFiles?.map((u) => u.toString()).toList(),
"codeName": code.name,
};
}
String toJsonString() {
JsonEncoder encoder = new JsonEncoder.withIndent(" ");
return encoder.convert(this);
}
}
class DiagnosticMessageFromJson implements DiagnosticMessage {
@override
final Iterable<String> ansiFormatted;
@override
final Iterable<String> plainTextFormatted;
@override
final Severity severity;
final Uri? uri;
@override
final List<Uri>? involvedFiles;
@override
final String codeName;
DiagnosticMessageFromJson(this.ansiFormatted, this.plainTextFormatted,
this.severity, this.uri, this.involvedFiles, this.codeName);
factory DiagnosticMessageFromJson.fromJson(String jsonString) {
Map<String, Object?> decoded = json.decode(jsonString);
List<String> ansiFormatted =
new List<String>.from(_asListOfString(decoded["ansiFormatted"]));
List<String> plainTextFormatted =
_asListOfString(decoded["plainTextFormatted"]);
Severity severity = Severity.values[decoded["severity"] as int];
Uri? uri =
decoded["uri"] == null ? null : Uri.parse(decoded["uri"] as String);
List<Uri>? involvedFiles = decoded["involvedFiles"] == null
? null
: _asListOfString(decoded["involvedFiles"])
.map((e) => Uri.parse(e))
.toList();
String codeName = decoded["codeName"] as String;
return new DiagnosticMessageFromJson(ansiFormatted, plainTextFormatted,
severity, uri, involvedFiles, codeName);
}
Map<String, Object?> toJson() {
// This should be kept in sync with package:kernel/problems.md
return <String, Object?>{
"ansiFormatted": ansiFormatted.toList(),
"plainTextFormatted": plainTextFormatted.toList(),
"severity": severity.index,
"uri": uri?.toString(),
"involvedFiles": involvedFiles?.map((u) => u.toString()).toList(),
"codeName": codeName,
};
}
String toJsonString() {
JsonEncoder encoder = new JsonEncoder.withIndent(" ");
return encoder.convert(this);
}
static List<String> _asListOfString(Object? value) {
return (value as List<dynamic>).cast<String>();
}
}
String? relativizeUri(Uri? uri) {
// We have this method here for two reasons:
//
// 1. It allows us to implement #uri message argument without using it
// (otherwise, we might get an `UNUSED_IMPORT` warning).
//
// 2. We can change `base` argument here if needed.
return uri == null ? null : util.relativizeUri(Uri.base, uri, util.isWindows);
}
typedef SummaryTemplate = Message Function(int, int, num, num, num);
String itemizeNames(List<String> names) {
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < names.length - 1; i++) {
buffer.write(" - ");
buffer.writeln(names[i]);
}
buffer.write(" - ");
buffer.write(names.last);
return "$buffer";
}
/// Convert the synthetic name of an implicit mixin application class
/// into a name suitable for user-faced strings.
///
/// For example, when compiling "class A extends S with M1, M2", the
/// two synthetic classes will be named "_A&S&M1" and "_A&S&M1&M2".
/// This function will return "S with M1" and "S with M1, M2", respectively.
///
/// This method is copied from package:kernel/ast.dart.
// TODO(johnniwinther): Avoid the need for this method.
String demangleMixinApplicationName(String name) {
List<String> nameParts = name.split('&');
if (nameParts.length < 2 || name == "&") return name;
String demangledName = nameParts[1];
for (int i = 2; i < nameParts.length; i++) {
demangledName += (i == 2 ? " with " : ", ") + nameParts[i];
}
return demangledName;
}
final RegExp templateKey = new RegExp(r'#(\w+)');
/// Replaces occurrences of '#key' in [template], where 'key' is a key in
/// [arguments], with the corresponding values.
String applyArgumentsToTemplate(
String template, Map<String, dynamic> arguments) {
// TODO(johnniwinther): Remove `as dynamic` when unsound null safety is
// no longer supported.
if (arguments as dynamic == null || arguments.isEmpty) {
assert(!template.contains(templateKey),
'Message requires arguments, but none were provided.');
return template;
}
return template.replaceAllMapped(templateKey, (Match match) {
String? key = match.group(1);
Object? value = arguments[key];
assert(value != null, "No value for '$key' found in $arguments");
return value.toString();
});
}

View file

@ -1,86 +0,0 @@
// Copyright (c) 2018, 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.
library front_end.diagnostic_message;
import 'codes.dart' show Code, DiagnosticMessageFromJson, FormattedMessage;
import 'severity.dart' show Severity;
/// The type of a diagnostic message callback. For example:
///
/// void handler(DiagnosticMessage message) {
/// if (enableTerminalColors) { // See [terminal_color_support.dart].
/// message.ansiFormatted.forEach(stderr.writeln);
/// } else {
/// message.plainTextFormatted.forEach(stderr.writeln);
/// }
/// }
typedef DiagnosticMessageHandler = void Function(DiagnosticMessage);
/// Represents a diagnostic message that can be reported from a tool, for
/// example, a compiler.
///
/// The word *diagnostic* is used loosely here, as a tool may also use this for
/// reporting any kind of message, including non-diagnostic messages such as
/// licensing, informal, or logging information. This allows a well-behaved
/// tool to never directly write to stdout or stderr.
abstract class DiagnosticMessage {
DiagnosticMessage._(); // Prevent subclassing.
Iterable<String> get ansiFormatted;
Iterable<String> get plainTextFormatted;
Severity get severity;
Iterable<Uri>? get involvedFiles;
String? get codeName;
}
/// This method is subject to change.
Uri? getMessageUri(DiagnosticMessage message) {
return message is FormattedMessage
? message.uri
: message is DiagnosticMessageFromJson
? message.uri
: null;
}
/// This method is subject to change.
int? getMessageCharOffset(DiagnosticMessage message) {
return message is FormattedMessage ? message.charOffset : null;
}
/// This method is subject to change.
int? getMessageLength(DiagnosticMessage message) {
return message is FormattedMessage ? message.length : null;
}
/// This method is subject to change.
Code? getMessageCodeObject(DiagnosticMessage message) {
return message is FormattedMessage ? message.code : null;
}
/// This method is subject to change.
String? getMessageHeaderText(DiagnosticMessage message) {
return message is FormattedMessage ? message.problemMessage : null;
}
/// This method is subject to change.
int getMessageCode(DiagnosticMessage message) {
return message is FormattedMessage ? message.code.index : -1;
}
/// This method is subject to change.
Map<String, dynamic>? getMessageArguments(DiagnosticMessage message) {
return message is FormattedMessage ? message.arguments : null;
}
/// This method is subject to change.
Iterable<DiagnosticMessage>? getMessageRelatedInformation(
DiagnosticMessage message) {
return message is FormattedMessage ? message.relatedInformation : null;
}

View file

@ -1,48 +0,0 @@
// Copyright (c) 2017, 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.
library _fe_analyzer_shared.messages.severity;
enum Severity {
context,
error,
ignored,
internalProblem,
warning,
info,
}
const Map<String, String> severityEnumNames = const <String, String>{
'CONTEXT': 'context',
'ERROR': 'error',
'IGNORED': 'ignored',
'INTERNAL_PROBLEM': 'internalProblem',
'WARNING': 'warning',
'INFO': 'info',
};
const Map<String, Severity> severityEnumValues = const <String, Severity>{
'CONTEXT': Severity.context,
'ERROR': Severity.error,
'IGNORED': Severity.ignored,
'INTERNAL_PROBLEM': Severity.internalProblem,
'WARNING': Severity.warning,
'INFO': Severity.info,
};
const Map<Severity, String> severityPrefixes = const <Severity, String>{
Severity.error: "Error",
Severity.internalProblem: "Internal problem",
Severity.warning: "Warning",
Severity.context: "Context",
Severity.info: "Info",
};
const Map<Severity, String> severityTexts = const <Severity, String>{
Severity.error: "error",
Severity.internalProblem: "internal problem",
Severity.warning: "warning",
Severity.context: "context",
Severity.info: "info",
};

View file

@ -1,49 +0,0 @@
// Copyright (c) 2024, 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 'expressions.dart';
import 'proto.dart';
/// Superclass for named and position arguments.
// TODO(johnniwinther): Merge subclasses into one class?
sealed class Argument {
/// Returns the [Argument] corresponding to this [Argument] in
/// which all [UnresolvedIdentifier]s have been resolved within their scope.
///
/// If this didn't create a new [Argument], `null` is returned.
Argument? resolve();
}
class PositionalArgument extends Argument {
final Expression expression;
PositionalArgument(this.expression);
@override
String toString() => 'PositionalArgument($expression)';
@override
Argument? resolve() {
Expression? newExpression = expression.resolve();
return newExpression == null ? null : new PositionalArgument(newExpression);
}
}
class NamedArgument extends Argument {
final String name;
final Expression expression;
NamedArgument(this.name, this.expression);
@override
String toString() => 'NamedArgument($name,$expression)';
@override
Argument? resolve() {
Expression? newExpression = expression.resolve();
return newExpression == null
? null
: new NamedArgument(name, newExpression);
}
}

View file

@ -1,13 +0,0 @@
// Copyright (c) 2024, 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.
export 'arguments.dart';
export 'elements.dart';
export 'expressions.dart';
export 'formal_parameters.dart';
export 'proto.dart';
export 'record_fields.dart';
export 'references.dart';
export 'string_literal_parts.dart';
export 'type_annotations.dart';

View file

@ -1,104 +0,0 @@
// Copyright (c) 2024, 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 'expressions.dart';
import 'proto.dart';
/// Superclass for collection elements.
sealed class Element {
/// Returns the [Element] corresponding to this [Element] in
/// which all [UnresolvedIdentifier]s have been resolved within their scope.
///
/// If this didn't create a new [Element], `null` is returned.
Element? resolve();
}
class ExpressionElement extends Element {
final Expression expression;
final bool isNullAware;
ExpressionElement(this.expression, {required this.isNullAware});
@override
String toString() =>
'ExpressionElement($expression,isNullAware=$isNullAware)';
@override
Element? resolve() {
Expression? newExpression = expression.resolve();
return newExpression == null
? null
: new ExpressionElement(newExpression, isNullAware: isNullAware);
}
}
class MapEntryElement extends Element {
final Expression key;
final Expression value;
final bool isNullAwareKey;
final bool isNullAwareValue;
MapEntryElement(this.key, this.value,
{required this.isNullAwareKey, required this.isNullAwareValue});
@override
String toString() => 'MapEntryElement($key,$value,'
'isNullAwareKey=$isNullAwareValue,isNullAwareValue=$isNullAwareValue)';
@override
Element? resolve() {
Expression? newKey = key.resolve();
Expression? newValue = value.resolve();
return newKey == null && newValue == null
? null
: new MapEntryElement(newKey ?? key, newValue ?? value,
isNullAwareKey: isNullAwareKey, isNullAwareValue: isNullAwareValue);
}
}
class SpreadElement extends Element {
final Expression expression;
final bool isNullAware;
SpreadElement(this.expression, {required this.isNullAware});
@override
String toString() => 'SpreadElement($expression,isNullAware=$isNullAware)';
@override
Element? resolve() {
Expression? newExpression = expression.resolve();
return newExpression == null
? null
: new SpreadElement(newExpression, isNullAware: isNullAware);
}
}
class IfElement extends Element {
final Expression condition;
final Element then;
final Element? otherwise;
IfElement(this.condition, this.then, [this.otherwise]);
@override
String toString() => 'IfElement($condition,$then,$otherwise)';
@override
Element? resolve() {
Expression? newCondition = condition.resolve();
Element? newThen = then.resolve();
Element? newOtherwise = otherwise?.resolve();
if (otherwise != null) {
return newCondition == null && newThen == null && newOtherwise == null
? null
: new IfElement(newCondition ?? condition, newThen ?? then,
newOtherwise ?? otherwise);
} else {
return newCondition == null && newThen == null
? null
: new IfElement(newCondition ?? condition, newThen ?? then);
}
}
}

View file

@ -1,634 +0,0 @@
// Copyright (c) 2024, 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 'ast.dart';
typedef GetFieldInitializer = Expression? Function(
FieldReference fieldReference);
/// Evaluates an [expression] based on the semantics that can be deduced from
/// the syntax.
///
/// If [getFieldInitializer] is provided, it is used to get constant initializer
/// expressions of const [StaticGet]s in [expression]. The evaluated constant
/// initializer expressions are used as the evaluation result of the
/// [StaticGet]s.
///
/// If [dereferences] is provided, the field references of [StaticGet]s, for
/// which [getFieldInitializer] has provided a constant initializer
/// [Expression], are mapped to there corresponding in constant initializer
/// [Expression]s in [dereferences].
Expression evaluateExpression(Expression expression,
{GetFieldInitializer? getFieldInitializer,
Map<FieldReference, Expression>? dereferences}) {
return new Evaluator(
getFieldInitializer: getFieldInitializer, dereferences: dereferences)
.evaluate(expression);
}
class Evaluator {
final GetFieldInitializer? _getFieldInitializer;
final Map<FieldReference, Expression>? _dereferences;
Evaluator(
{required GetFieldInitializer? getFieldInitializer,
required Map<FieldReference, Expression>? dereferences})
: _getFieldInitializer = getFieldInitializer,
_dereferences = dereferences;
Expression evaluate(Expression expression) {
return _visitExpression(expression);
}
Expression _visitExpression(Expression expression) {
switch (expression) {
case StaticGet():
if (_getFieldInitializer != null) {
Expression? result = _getFieldInitializer(expression.reference);
if (result != null) {
if (_dereferences != null) {
_dereferences[expression.reference] = result;
}
return _visitExpression(result);
}
}
return expression;
case InvalidExpression():
case FunctionTearOff():
case ConstructorTearOff():
case IntegerLiteral():
case DoubleLiteral():
case BooleanLiteral():
case NullLiteral():
case SymbolLiteral():
return expression;
case ConstructorInvocation():
return new ConstructorInvocation(expression.type,
expression.constructor, _visitArguments(expression.arguments));
case StringLiteral():
return _visitStringLiteral(expression);
case AdjacentStringLiterals():
return _visitAdjacentStringLiterals(expression);
case ImplicitInvocation():
return new ImplicitInvocation(_visitExpression(expression.receiver),
expression.typeArguments, _visitArguments(expression.arguments));
case StaticInvocation():
return new StaticInvocation(expression.function,
expression.typeArguments, _visitArguments(expression.arguments));
case Instantiation():
return new Instantiation(
_visitExpression(expression.receiver), expression.typeArguments);
case MethodInvocation():
return new MethodInvocation(
_visitExpression(expression.receiver),
expression.name,
expression.typeArguments,
_visitArguments(expression.arguments));
case PropertyGet():
Expression receiver = _visitExpression(expression.receiver);
if (expression.name == 'length') {
if (receiver case StringLiteral(parts: [StringPart(:String text)])) {
return new IntegerLiteral.fromValue(text.length);
}
}
return new PropertyGet(receiver, expression.name);
case NullAwarePropertyGet():
Expression receiver = _visitExpression(expression.receiver);
return switch (_isNull(receiver)) {
NullValue.isNull => new NullLiteral(),
NullValue.isNonNull =>
_visitExpression(new PropertyGet(receiver, expression.name)),
NullValue.unknown =>
new NullAwarePropertyGet(receiver, expression.name),
};
case TypeLiteral():
return expression;
case ParenthesizedExpression():
return _visitExpression(expression.expression);
case ConditionalExpression():
Expression condition = _visitExpression(expression.condition);
return switch (condition) {
BooleanLiteral(value: true) => _visitExpression(expression.then),
BooleanLiteral(value: false) =>
_visitExpression(expression.otherwise),
_ => new ConditionalExpression(
condition,
_visitExpression(expression.then),
_visitExpression(expression.otherwise)),
};
case ListLiteral():
return new ListLiteral(
expression.typeArguments, _visitElements(expression.elements));
case SetOrMapLiteral():
return new SetOrMapLiteral(
expression.typeArguments, _visitElements(expression.elements));
case RecordLiteral():
return new RecordLiteral(_visitRecordFields(expression.fields));
case IfNull():
Expression left = _visitExpression(expression.left);
return switch (_isNull(left)) {
NullValue.isNull => _visitExpression(expression.right),
NullValue.isNonNull => left,
NullValue.unknown =>
new IfNull(left, _visitExpression(expression.right)),
};
case LogicalExpression():
return _visitLogicalExpression(expression);
case EqualityExpression():
return _visitEqualityExpression(expression);
case BinaryExpression():
return _visitBinaryExpression(expression);
case UnaryExpression():
return _visitUnaryExpression(expression);
case IsTest():
return new IsTest(
_visitExpression(expression.expression), expression.type,
isNot: expression.isNot);
case AsExpression():
return new AsExpression(
_visitExpression(expression.expression), expression.type);
case NullCheck():
Expression operand = _visitExpression(expression.expression);
return switch (_isNull(operand)) {
// This is known to fail but we have no way to represent failure.
NullValue.isNull => new NullCheck(operand),
NullValue.isNonNull => operand,
NullValue.unknown => new NullCheck(operand)
};
case UnresolvedExpression():
return expression;
}
}
Expression _visitStringLiteral(StringLiteral expression) {
if (expression.parts.length == 1 && expression.parts.single is StringPart) {
return expression;
}
List<StringLiteralPart> evaluatedParts = [];
StringBuffer? stringBuffer;
void flush() {
if (stringBuffer != null) {
String text = stringBuffer.toString();
if (text.isNotEmpty) {
evaluatedParts.add(new StringPart(stringBuffer.toString()));
}
stringBuffer = null;
}
}
for (StringLiteralPart part in expression.parts) {
for (StringLiteralPart evaluatedPart in _visitStringLiteralPart(part)) {
switch (evaluatedPart) {
case StringPart():
(stringBuffer ??= new StringBuffer()).write(evaluatedPart.text);
case InterpolationPart():
flush();
evaluatedParts.add(evaluatedPart);
}
}
}
flush();
if (evaluatedParts.isEmpty) {
evaluatedParts.add(new StringPart(stringBuffer.toString()));
}
return new StringLiteral(evaluatedParts);
}
Expression _visitAdjacentStringLiterals(AdjacentStringLiterals expression) {
List<Expression> evaluatedParts = [];
List<StringLiteralPart>? stringLiteralParts;
void flush() {
if (stringLiteralParts != null) {
StringLiteral stringLiteral = new StringLiteral(stringLiteralParts);
evaluatedParts.add(_visitExpression(stringLiteral));
}
}
for (Expression part in expression.expressions) {
Expression evaluatedPart = _visitExpression(part);
switch (evaluatedPart) {
case StringLiteral(:List<StringLiteralPart> parts):
(stringLiteralParts ??= []).addAll(parts);
default:
flush();
evaluatedParts.add(evaluatedPart);
}
}
flush();
if (evaluatedParts.length == 1) {
return evaluatedParts.single;
}
return new AdjacentStringLiterals(evaluatedParts);
}
Expression _visitLogicalExpression(LogicalExpression expression) {
Expression left = _visitExpression(expression.left);
return switch (left) {
BooleanLiteral(value: true) => switch (expression.operator) {
LogicalOperator.and => _visitExpression(expression.right),
LogicalOperator.or => left,
},
BooleanLiteral(value: false) => switch (expression.operator) {
LogicalOperator.and => left,
LogicalOperator.or => _visitExpression(expression.right),
},
_ => new LogicalExpression(
left, expression.operator, _visitExpression(expression.right)),
};
}
Expression _visitEqualityExpression(EqualityExpression expression) {
Expression leftExpression = _visitExpression(expression.left);
Expression rightExpression = _visitExpression(expression.right);
switch ((leftExpression, rightExpression)) {
case (
NullLiteral(),
NullLiteral(),
):
return new BooleanLiteral(!expression.isNotEquals);
case (IntegerLiteral(value: int left), IntegerLiteral(value: int right)):
return new BooleanLiteral(
expression.isNotEquals ? left != right : left == right);
default:
// TODO(johnniwinther): Support all cases.
return new EqualityExpression(leftExpression, rightExpression,
isNotEquals: expression.isNotEquals);
}
}
Expression? _visitBinaryIntExpression(
int left, BinaryOperator operator, int right) {
switch (operator) {
case BinaryOperator.lessThan:
return new BooleanLiteral(left < right);
case BinaryOperator.lessThanOrEqual:
return new BooleanLiteral(left <= right);
case BinaryOperator.greaterThan:
return new BooleanLiteral(left > right);
case BinaryOperator.greaterThanOrEqual:
return new BooleanLiteral(left >= right);
case BinaryOperator.bitwiseOr:
return new IntegerLiteral.fromValue(left | right);
case BinaryOperator.bitwiseXor:
return new IntegerLiteral.fromValue(left ^ right);
case BinaryOperator.bitwiseAnd:
return new IntegerLiteral.fromValue(left & right);
case BinaryOperator.shiftLeft:
return new IntegerLiteral.fromValue(left << right);
case BinaryOperator.unsignedShiftRight:
return new IntegerLiteral.fromValue(left >> right);
case BinaryOperator.signedShiftRight:
return new IntegerLiteral.fromValue(left >>> right);
case BinaryOperator.plus:
return new IntegerLiteral.fromValue(left + right);
case BinaryOperator.minus:
return new IntegerLiteral.fromValue(left - right);
case BinaryOperator.times:
return new IntegerLiteral.fromValue(left * right);
case BinaryOperator.divide:
if (right != 0) {
double value = left / right;
return new DoubleLiteral('$value', value);
}
case BinaryOperator.modulo:
if (right != 0) {
return new IntegerLiteral.fromValue(left % right);
}
case BinaryOperator.integerDivide:
if (right != 0) {
return new IntegerLiteral.fromValue(left ~/ right);
}
}
return null;
}
Expression _visitBinaryExpression(BinaryExpression expression) {
Expression leftExpression = _visitExpression(expression.left);
Expression rightExpression = _visitExpression(expression.right);
switch ((leftExpression, rightExpression)) {
case (IntegerLiteral(value: int left), IntegerLiteral(value: int right)):
return _visitBinaryIntExpression(left, expression.operator, right) ??
new BinaryExpression(
leftExpression, expression.operator, rightExpression);
default:
// TODO(johnniwinther): Support more cases.
return new BinaryExpression(
leftExpression, expression.operator, rightExpression);
}
}
Expression _visitUnaryExpression(UnaryExpression expression) {
Expression operand = _visitExpression(expression.expression);
switch ((expression.operator, operand)) {
case (UnaryOperator.minus, IntegerLiteral(:int value)):
return new IntegerLiteral.fromValue(-value);
case (UnaryOperator.bang, BooleanLiteral(:bool value)):
return new BooleanLiteral(!value);
case (UnaryOperator.tilde, IntegerLiteral(:int value)):
return new IntegerLiteral.fromValue(~value);
default:
// TODO(johnniwinther): Support more cases.
return new UnaryExpression(expression.operator, operand);
}
}
List<StringLiteralPart> _visitStringLiteralPart(StringLiteralPart part) {
switch (part) {
case StringPart():
return [part];
case InterpolationPart():
Expression expression = _visitExpression(part.expression);
return switch (expression) {
StringLiteral() => expression.parts,
NullLiteral() => [new StringPart('null')],
BooleanLiteral(:bool value) => [new StringPart('$value')],
IntegerLiteral(:int value) => [new StringPart('$value')],
DoubleLiteral(:double value) => [new StringPart('$value')],
_ => [new InterpolationPart(expression)],
};
}
}
List<Argument> _visitArguments(List<Argument> arguments) {
List<Argument> list = [];
for (Argument argument in arguments) {
switch (argument) {
case PositionalArgument():
list.add(
new PositionalArgument(_visitExpression(argument.expression)));
case NamedArgument():
list.add(new NamedArgument(
argument.name, _visitExpression(argument.expression)));
}
}
return list;
}
List<RecordField> _visitRecordFields(List<RecordField> fields) {
List<RecordField> list = [];
for (RecordField field in fields) {
switch (field) {
case RecordNamedField():
list.add(new RecordNamedField(
field.name, _visitExpression(field.expression)));
case RecordPositionalField():
list.add(
new RecordPositionalField(_visitExpression(field.expression)));
}
}
return list;
}
List<Element> _visitElements(List<Element> elements) {
List<Element> list = [];
for (Element element in elements) {
switch (element) {
case ExpressionElement():
Expression expression = _visitExpression(element.expression);
bool isNullAware = element.isNullAware;
if (isNullAware) {
switch (_isNull(expression)) {
case NullValue.isNull:
// Skip element.
continue;
case NullValue.isNonNull:
isNullAware = false;
case NullValue.unknown:
}
}
list.add(new ExpressionElement(expression, isNullAware: isNullAware));
case MapEntryElement():
Expression key = _visitExpression(element.key);
Expression value = _visitExpression(element.value);
bool isNullAwareKey = element.isNullAwareKey;
bool isNullAwareValue = element.isNullAwareValue;
if (isNullAwareKey) {
switch (_isNull(key)) {
case NullValue.isNull:
// Skip entry.
continue;
case NullValue.isNonNull:
isNullAwareKey = false;
case NullValue.unknown:
}
}
if (isNullAwareValue) {
switch (_isNull(value)) {
case NullValue.isNull:
// Skip entry.
continue;
case NullValue.isNonNull:
isNullAwareValue = false;
case NullValue.unknown:
}
}
list.add(new MapEntryElement(key, value,
isNullAwareKey: isNullAwareKey,
isNullAwareValue: isNullAwareValue));
case SpreadElement():
Expression expression = _visitExpression(element.expression);
bool isNullAware = element.isNullAware;
if (isNullAware) {
switch (_isNull(expression)) {
case NullValue.isNull:
// Skip element.
continue;
case NullValue.isNonNull:
isNullAware = false;
case NullValue.unknown:
}
}
if (isNullAware) {
list.add(new SpreadElement(expression, isNullAware: true));
} else {
switch (expression) {
case ListLiteral():
list.addAll(_visitElements(expression.elements));
case SetOrMapLiteral():
list.addAll(_visitElements(expression.elements));
default:
list.add(new SpreadElement(expression, isNullAware: false));
}
}
case IfElement():
Expression condition = _visitExpression(element.condition);
switch (condition) {
case BooleanLiteral(value: true):
list.addAll(_visitElements([element.then]));
case BooleanLiteral(value: false):
if (element.otherwise != null) {
list.addAll(_visitElements([element.otherwise!]));
}
default:
Element? then = _visitElement(element.then);
Element? otherwise = element.otherwise != null
? _visitElement(element.otherwise!)
: null;
if (then != null) {
list.add(new IfElement(condition, then, otherwise));
} else if (otherwise != null) {
list.add(new IfElement(
new UnaryExpression(UnaryOperator.bang, condition),
otherwise));
} else {
// Skip element.
continue;
}
}
}
}
return list;
}
Element? _visitElement(Element element) {
switch (element) {
case ExpressionElement():
Expression expression = _visitExpression(element.expression);
bool isNullAware = element.isNullAware;
if (isNullAware) {
switch (_isNull(expression)) {
case NullValue.isNull:
// Skip element.
return null;
case NullValue.isNonNull:
isNullAware = false;
case NullValue.unknown:
}
}
return new ExpressionElement(expression,
isNullAware: element.isNullAware);
case MapEntryElement():
Expression key = _visitExpression(element.key);
Expression value = _visitExpression(element.value);
bool isNullAwareKey = element.isNullAwareKey;
bool isNullAwareValue = element.isNullAwareValue;
if (isNullAwareKey) {
switch (_isNull(key)) {
case NullValue.isNull:
// Skip entry.
return null;
case NullValue.isNonNull:
isNullAwareKey = false;
case NullValue.unknown:
}
}
if (isNullAwareValue) {
switch (_isNull(value)) {
case NullValue.isNull:
// Skip entry.
return null;
case NullValue.isNonNull:
isNullAwareValue = false;
case NullValue.unknown:
}
}
return new MapEntryElement(key, value,
isNullAwareKey: isNullAwareKey, isNullAwareValue: isNullAwareValue);
case SpreadElement():
Expression expression = _visitExpression(element.expression);
bool isNullAware = element.isNullAware;
if (isNullAware) {
switch (_isNull(expression)) {
case NullValue.isNull:
// Skip element.
return null;
case NullValue.isNonNull:
isNullAware = false;
case NullValue.unknown:
}
}
if (isNullAware) {
return new SpreadElement(expression, isNullAware: true);
} else {
switch (expression) {
case ListLiteral(elements: []):
case SetOrMapLiteral(elements: []):
// Empty spread.
return null;
default:
return new SpreadElement(expression, isNullAware: false);
}
}
case IfElement():
Expression condition = _visitExpression(element.condition);
switch (condition) {
case BooleanLiteral(value: true):
return _visitElement(element.then);
case BooleanLiteral(value: false):
if (element.otherwise != null) {
return _visitElement(element.otherwise!);
} else {
return null;
}
default:
Element? then = _visitElement(element.then);
Element? otherwise = element.otherwise != null
? _visitElement(element.otherwise!)
: null;
if (then != null) {
return new IfElement(condition, then, otherwise);
} else if (otherwise != null) {
return new IfElement(
new UnaryExpression(UnaryOperator.bang, condition),
otherwise);
} else {
// Skip element.
return null;
}
}
}
}
NullValue _isNull(Expression expression) {
return switch (expression) {
NullLiteral() => NullValue.isNull,
BooleanLiteral() => NullValue.isNonNull,
IntegerLiteral() => NullValue.isNonNull,
DoubleLiteral() => NullValue.isNonNull,
StringLiteral() => NullValue.isNonNull,
SymbolLiteral() => NullValue.isNonNull,
AdjacentStringLiterals() => NullValue.isNonNull,
EqualityExpression() => NullValue.isNonNull,
FunctionTearOff() => NullValue.isNonNull,
IsTest() => NullValue.isNonNull,
ListLiteral() => NullValue.isNonNull,
SetOrMapLiteral() => NullValue.isNonNull,
LogicalExpression() => NullValue.isNonNull,
ConstructorTearOff() => NullValue.isNonNull,
ConstructorInvocation() => NullValue.isNonNull,
Instantiation() => NullValue.isNonNull,
TypeLiteral() => NullValue.isNonNull,
RecordLiteral() => NullValue.isNonNull,
NullCheck() => NullValue.isNonNull,
// TODO(johnniwinther): Should the subexpressions be visited?
ParenthesizedExpression() => NullValue.unknown,
ConditionalExpression() => NullValue.unknown,
IfNull() => NullValue.unknown,
BinaryExpression() => NullValue.unknown,
UnaryExpression() => NullValue.unknown,
PropertyGet() => NullValue.unknown,
NullAwarePropertyGet() => NullValue.unknown,
StaticGet() => NullValue.unknown,
StaticInvocation() => NullValue.unknown,
InvalidExpression() => NullValue.unknown,
ImplicitInvocation() => NullValue.unknown,
MethodInvocation() => NullValue.unknown,
AsExpression() => NullValue.unknown,
UnresolvedExpression() => NullValue.unknown,
};
}
}
enum NullValue {
isNull,
isNonNull,
unknown,
}

Some files were not shown because too many files have changed in this diff Show more