2a98f98098
Change-Id: Ief30fb79b2afcbb3f50d9127eb622fac362b07b1 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/340141 Commit-Queue: Devon Carew <devoncarew@google.com> Auto-Submit: Parker Lougheed <parlough@gmail.com> Reviewed-by: Devon Carew <devoncarew@google.com> Reviewed-by: Bob Nystrom <rnystrom@google.com>
320 lines
9.8 KiB
Dart
320 lines
9.8 KiB
Dart
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
import 'package:analyzer/dart/ast/ast.dart';
|
|
import 'package:analyzer/dart/ast/token.dart';
|
|
import 'package:scrape/scrape.dart';
|
|
|
|
enum ArgumentMatch {
|
|
noArguments,
|
|
none,
|
|
all,
|
|
some,
|
|
prefix,
|
|
suffix,
|
|
middle,
|
|
noncontiguous
|
|
}
|
|
|
|
extension on ArgumentMatch {
|
|
String get description {
|
|
switch (this) {
|
|
case ArgumentMatch.noArguments:
|
|
return 'No arguments to match';
|
|
case ArgumentMatch.none:
|
|
return 'Matched none';
|
|
case ArgumentMatch.all:
|
|
return 'Matched all';
|
|
case ArgumentMatch.some:
|
|
return 'Matched some';
|
|
case ArgumentMatch.prefix:
|
|
return 'Matched prefix';
|
|
case ArgumentMatch.suffix:
|
|
return 'Matched suffix';
|
|
case ArgumentMatch.middle:
|
|
return 'Matched middle';
|
|
case ArgumentMatch.noncontiguous:
|
|
return 'Matched noncontiguous';
|
|
}
|
|
}
|
|
}
|
|
|
|
void main(List<String> arguments) {
|
|
Scrape()
|
|
..addHistogram('Potential use')
|
|
..addHistogram('Individual arguments')
|
|
..addHistogram('Named arguments')
|
|
..addHistogram('Positional arguments')
|
|
..addHistogram('Argument pattern')
|
|
..addHistogram('Append super args')
|
|
..addHistogram('Prepend super args')
|
|
..addHistogram('Insert super args')
|
|
..addHistogram('Do not merge super args')
|
|
..addHistogram('No explicit super(), call unnamed')
|
|
..addHistogram('No explicit super(), call same name')
|
|
..addVisitor(SuperclassParameterVisitor.new)
|
|
..runCommandLine(arguments);
|
|
}
|
|
|
|
class SuperclassParameterVisitor extends ScrapeVisitor {
|
|
@override
|
|
void visitConstructorDeclaration(ConstructorDeclaration node) {
|
|
// Whether the constructor might benefit from the feature at all.
|
|
var initializer = _findSuper(node);
|
|
if (initializer == null) {
|
|
record('Potential use', 'No: No initializer');
|
|
return;
|
|
}
|
|
|
|
if (initializer.argumentList.arguments.isEmpty) {
|
|
record('Potential use', 'No: Empty super() argument list');
|
|
return;
|
|
}
|
|
|
|
record('Potential use', 'Yes');
|
|
|
|
// If we get here, we have a superclass constructor call with arguments.
|
|
// See if any of them could use the feature.
|
|
var positionalParamNames = node.parameters.parameters
|
|
.where((param) => param.isPositional)
|
|
.map((param) => param.name!.lexeme)
|
|
.toList();
|
|
|
|
var namedParamNames = node.parameters.parameters
|
|
.where((param) => param.isNamed)
|
|
.map((param) => param.name!.lexeme)
|
|
.toSet();
|
|
|
|
var matchedNamedArguments = 0;
|
|
var unmatchedNamedArguments = 0;
|
|
|
|
var lastPositionalParam = -1;
|
|
var matchedIndexes = <int>[];
|
|
var unmatchedPositionalArguments = 0;
|
|
var positionalArgCount = 0;
|
|
for (var i = 0; i < initializer.argumentList.arguments.length; i++) {
|
|
var argument = initializer.argumentList.arguments[i];
|
|
|
|
if (argument is NamedExpression) {
|
|
var expression = argument.expression;
|
|
if (expression is! SimpleIdentifier) {
|
|
record('Individual arguments',
|
|
'Named argument expression is not identifier');
|
|
unmatchedNamedArguments++;
|
|
} else if (argument.name.label.name != expression.name) {
|
|
record('Individual arguments',
|
|
'Named argument name does not match expression name');
|
|
unmatchedNamedArguments++;
|
|
} else if (!namedParamNames.contains(expression.name)) {
|
|
record('Individual arguments',
|
|
'Named argument does not match a parameter');
|
|
unmatchedNamedArguments++;
|
|
} else {
|
|
record('Individual arguments', 'Argument matches a parameter');
|
|
matchedNamedArguments++;
|
|
}
|
|
} else {
|
|
positionalArgCount++;
|
|
if (argument is! SimpleIdentifier) {
|
|
record('Individual arguments',
|
|
'Positional argument expression is not identifier');
|
|
unmatchedPositionalArguments++;
|
|
} else {
|
|
// Start searching after the last matched positional parameter because
|
|
// we don't allow reordering them. If two arguments are out of order,
|
|
// that doesn't mean we can't use "super." at all, just that we can
|
|
// only use it for *one* of those arguments.
|
|
var index =
|
|
positionalParamNames.indexOf(argument.name, lastPositionalParam);
|
|
if (index == -1) {
|
|
record('Individual arguments',
|
|
'Positional argument does not match a parameter');
|
|
} else {
|
|
record('Individual arguments', 'Argument matches a parameter');
|
|
lastPositionalParam = index;
|
|
matchedIndexes.add(i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Characterize the positional argument list.
|
|
ArgumentMatch positionalMatch;
|
|
if (unmatchedPositionalArguments == 0) {
|
|
if (matchedIndexes.isEmpty) {
|
|
positionalMatch = ArgumentMatch.noArguments;
|
|
} else {
|
|
positionalMatch = ArgumentMatch.all;
|
|
}
|
|
} else if (matchedIndexes.isEmpty) {
|
|
positionalMatch = ArgumentMatch.none;
|
|
} else {
|
|
// If there is any unmatched argument before a matched one, then the
|
|
// matched arguments are not all at the beginning.
|
|
var matchedArePrefix = true;
|
|
for (var i = 1; i < positionalArgCount; i++) {
|
|
if (!matchedIndexes.contains(i - 1) && matchedIndexes.contains(i)) {
|
|
matchedArePrefix = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If there is any unmatched argument after a matched one, then the
|
|
// matched arguments are not all at the end.
|
|
var matchedAreSuffix = true;
|
|
for (var i = 0; i < positionalArgCount - 1; i++) {
|
|
if (!matchedIndexes.contains(i + 1) && matchedIndexes.contains(i)) {
|
|
matchedAreSuffix = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If any index between the first and last matched arg is not matched,
|
|
// then the arguments are not contiguous.
|
|
var matchedAreContiguous = true;
|
|
if (matchedIndexes.isNotEmpty) {
|
|
for (var i = matchedIndexes.first; i <= matchedIndexes.last; i++) {
|
|
if (!matchedIndexes.contains(i)) {
|
|
matchedAreContiguous = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!matchedAreContiguous) {
|
|
positionalMatch = ArgumentMatch.noncontiguous;
|
|
} else if (matchedArePrefix) {
|
|
positionalMatch = ArgumentMatch.prefix;
|
|
} else if (matchedAreSuffix) {
|
|
positionalMatch = ArgumentMatch.suffix;
|
|
} else {
|
|
positionalMatch = ArgumentMatch.middle;
|
|
}
|
|
}
|
|
|
|
record('Positional arguments', positionalMatch.description);
|
|
|
|
// Characterize the named argument list.
|
|
ArgumentMatch namedMatch;
|
|
if (matchedNamedArguments == 0) {
|
|
if (unmatchedNamedArguments == 0) {
|
|
namedMatch = ArgumentMatch.noArguments;
|
|
} else {
|
|
namedMatch = ArgumentMatch.none;
|
|
}
|
|
} else {
|
|
if (unmatchedNamedArguments == 0) {
|
|
namedMatch = ArgumentMatch.all;
|
|
} else {
|
|
namedMatch = ArgumentMatch.some;
|
|
}
|
|
}
|
|
|
|
record('Named arguments', namedMatch.description);
|
|
|
|
var pattern = [
|
|
for (var i = 0; i < positionalArgCount; i++)
|
|
matchedIndexes.contains(i) ? 's' : '_',
|
|
for (var i = 0; i < matchedNamedArguments; i++) ':s',
|
|
for (var i = 0; i < unmatchedNamedArguments; i++) ':_',
|
|
].join(',');
|
|
record('Argument pattern', '($pattern)');
|
|
|
|
// If none of the arguments could be 'super.', then none of the proposals
|
|
// apply.
|
|
if (matchedIndexes.isEmpty && matchedNamedArguments == 0) return;
|
|
|
|
var append = true;
|
|
var prepend = true;
|
|
var insert = true;
|
|
var noMerge = true;
|
|
var allParams = true;
|
|
|
|
switch (positionalMatch) {
|
|
case ArgumentMatch.noArguments:
|
|
case ArgumentMatch.all:
|
|
// OK.
|
|
break;
|
|
|
|
case ArgumentMatch.none:
|
|
allParams = false;
|
|
break;
|
|
|
|
case ArgumentMatch.some:
|
|
throw Exception('Should not get some for positional args.');
|
|
|
|
case ArgumentMatch.prefix:
|
|
append = false;
|
|
noMerge = false;
|
|
allParams = false;
|
|
break;
|
|
|
|
case ArgumentMatch.suffix:
|
|
prepend = false;
|
|
noMerge = false;
|
|
allParams = false;
|
|
break;
|
|
|
|
case ArgumentMatch.middle:
|
|
append = false;
|
|
prepend = false;
|
|
noMerge = false;
|
|
allParams = false;
|
|
break;
|
|
|
|
case ArgumentMatch.noncontiguous:
|
|
append = false;
|
|
prepend = false;
|
|
insert = false;
|
|
noMerge = false;
|
|
allParams = false;
|
|
break;
|
|
}
|
|
|
|
switch (namedMatch) {
|
|
case ArgumentMatch.noArguments:
|
|
case ArgumentMatch.all:
|
|
// OK.
|
|
break;
|
|
|
|
case ArgumentMatch.none:
|
|
case ArgumentMatch.some:
|
|
allParams = false;
|
|
break;
|
|
|
|
default:
|
|
throw Exception('Unexpected match.');
|
|
}
|
|
|
|
record('Append super args', append ? 'Yes' : 'No');
|
|
record('Prepend super args', prepend ? 'Yes' : 'No');
|
|
record('Insert super args', insert ? 'Yes' : 'No');
|
|
record('Do not merge super args', noMerge ? 'Yes' : 'No');
|
|
|
|
var subName = _constructorName(node.name);
|
|
var superName = _constructorName(initializer.constructorName?.token);
|
|
|
|
record('No explicit super(), call same name',
|
|
(allParams && superName == subName) ? 'Yes' : 'No');
|
|
|
|
record('No explicit super(), call unnamed',
|
|
(allParams && superName == '(unnamed)') ? 'Yes' : 'No');
|
|
}
|
|
|
|
String _constructorName(Token? name) {
|
|
if (name == null) return '(unnamed)';
|
|
return name.lexeme;
|
|
}
|
|
|
|
SuperConstructorInvocation? _findSuper(ConstructorDeclaration node) {
|
|
for (var initializer in node.initializers) {
|
|
if (initializer is SuperConstructorInvocation) {
|
|
return initializer;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|