remove .pub-cache
This commit is contained in:
parent
666fa20e7b
commit
e98b668639
4378 changed files with 1 additions and 1464969 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -15,3 +15,4 @@
|
|||
/d4rt-formulas.iml
|
||||
/build/
|
||||
/d4rt_formulas.iml
|
||||
.aider*
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -1 +0,0 @@
|
|||
da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f
|
||||
|
|
@ -1 +0,0 @@
|
|||
974859dc0ff5f37bc4313244b3218c791810d03ab3470a579580279ba971a48d
|
||||
|
|
@ -1 +0,0 @@
|
|||
d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
|
||||
|
|
@ -1 +0,0 @@
|
|||
758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb
|
||||
|
|
@ -1 +0,0 @@
|
|||
8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea
|
||||
|
|
@ -1 +0,0 @@
|
|||
ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec
|
||||
|
|
@ -1 +0,0 @@
|
|||
2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76
|
||||
|
|
@ -1 +0,0 @@
|
|||
b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
|
||||
|
|
@ -1 +0,0 @@
|
|||
5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d
|
||||
|
|
@ -1 +0,0 @@
|
|||
1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855
|
||||
|
|
@ -1 +0,0 @@
|
|||
4220081caf1cea231e127a8fd2801b4b55464a51f840b56bb079ce2b3792e9e6
|
||||
|
|
@ -1 +0,0 @@
|
|||
a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||
|
|
@ -1 +0,0 @@
|
|||
f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
|
||||
|
|
@ -1 +0,0 @@
|
|||
c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
|
||||
|
|
@ -1 +0,0 @@
|
|||
aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
|
||||
|
|
@ -1 +0,0 @@
|
|||
178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571
|
||||
|
|
@ -1 +0,0 @@
|
|||
dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
|
||||
|
|
@ -1 +0,0 @@
|
|||
53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc
|
||||
|
|
@ -1 +0,0 @@
|
|||
c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
|
||||
|
|
@ -1 +0,0 @@
|
|||
c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
|
||||
|
|
@ -1 +0,0 @@
|
|||
dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||
|
|
@ -1 +0,0 @@
|
|||
23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394
|
||||
|
|
@ -1 +0,0 @@
|
|||
41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6
|
||||
|
|
@ -1 +0,0 @@
|
|||
6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db
|
||||
|
|
@ -1 +0,0 @@
|
|||
f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc
|
||||
|
|
@ -1 +0,0 @@
|
|||
75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5
|
||||
|
|
@ -1 +0,0 @@
|
|||
20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a
|
||||
|
|
@ -1 +0,0 @@
|
|||
5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585
|
||||
|
|
@ -1 +0,0 @@
|
|||
e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
|
||||
|
|
@ -1 +0,0 @@
|
|||
89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e
|
||||
|
|
@ -1 +0,0 @@
|
|||
c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3
|
||||
|
|
@ -1 +0,0 @@
|
|||
3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925
|
||||
|
|
@ -1 +0,0 @@
|
|||
c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b
|
||||
|
|
@ -1 +0,0 @@
|
|||
190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812
|
||||
|
|
@ -1 +0,0 @@
|
|||
254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c
|
||||
|
|
@ -1 +0,0 @@
|
|||
8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1
|
||||
|
|
@ -1 +0,0 @@
|
|||
969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d
|
||||
|
|
@ -1 +0,0 @@
|
|||
921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43
|
||||
|
|
@ -1 +0,0 @@
|
|||
7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e
|
||||
|
|
@ -1 +0,0 @@
|
|||
75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7
|
||||
|
|
@ -1 +0,0 @@
|
|||
ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
|
||||
|
|
@ -1 +0,0 @@
|
|||
0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0
|
||||
|
|
@ -1 +0,0 @@
|
|||
f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||
|
|
@ -1 +0,0 @@
|
|||
45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60
|
||||
|
|
@ -1 +0,0 @@
|
|||
0b7fd4a0bbc4b92641dbf20adfd7e3fd1398fe17102d94b674234563e110088a
|
||||
|
|
@ -1 +0,0 @@
|
|||
868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a
|
||||
|
|
@ -1 +0,0 @@
|
|||
34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c
|
||||
|
|
@ -1 +0,0 @@
|
|||
d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
|
||||
|
|
@ -1 +0,0 @@
|
|||
87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572
|
||||
|
|
@ -1 +0,0 @@
|
|||
b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
|
||||
|
|
@ -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.
|
||||
|
|
@ -1 +0,0 @@
|
|||
file:/tools/OWNERS_MODEL
|
||||
|
|
@ -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)
|
||||
|
|
@ -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.
|
||||
|
|
@ -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
|
||||
|
|
@ -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/**
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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)');
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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});
|
||||
}
|
||||
|
|
@ -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 = {};
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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.';
|
||||
}
|
||||
|
|
@ -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)';
|
||||
}
|
||||
|
|
@ -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}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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('|');
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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];
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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",
|
||||
};
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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';
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
Loading…
Reference in a new issue