34fb48bb8a
Migrates libraries dependent only on already migrated libraries. Change-Id: I0e85ee8dbc2afce031b92e0009e71c206a55af28 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/179502 Commit-Queue: Johnni Winther <johnniwinther@google.com> Reviewed-by: Jens Johansen <jensj@google.com>
431 lines
15 KiB
Dart
431 lines
15 KiB
Dart
// 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.
|
|
|
|
// @dart=2.9
|
|
|
|
import "dart:io" show File, Platform, stdout;
|
|
import "dart:typed_data" show Uint8List;
|
|
|
|
import 'package:_fe_analyzer_shared/src/scanner/token.dart';
|
|
import 'package:analyzer/dart/ast/token.dart';
|
|
import "package:front_end/src/fasta/util/direct_parser_ast.dart";
|
|
import "package:front_end/src/fasta/util/direct_parser_ast_helper.dart"
|
|
show DirectParserASTContent;
|
|
import 'package:_fe_analyzer_shared/src/parser/parser.dart'
|
|
show IdentifierContext;
|
|
import 'package:front_end/src/fasta/util/direct_parser_ast_helper.dart';
|
|
|
|
void main(List<String> args) {
|
|
Uri uri = Platform.script;
|
|
uri = uri.resolve("../../kernel/lib/ast.dart");
|
|
Uint8List bytes = new File.fromUri(uri).readAsBytesSync();
|
|
DirectParserASTContentCompilationUnitEnd ast =
|
|
getAST(bytes, includeBody: true, includeComments: true);
|
|
Map<String, DirectParserASTContentTopLevelDeclarationEnd> classes = {};
|
|
for (DirectParserASTContentTopLevelDeclarationEnd cls in ast.getClasses()) {
|
|
DirectParserASTContentIdentifierHandle identifier = cls.getIdentifier();
|
|
assert(classes[identifier.token] == null);
|
|
classes[identifier.token.toString()] = cls;
|
|
}
|
|
|
|
Set<String> goodNames = {"TreeNode"};
|
|
Map<Token, Replacement> replacements = {};
|
|
for (MapEntry<String, DirectParserASTContentTopLevelDeclarationEnd> entry
|
|
in classes.entries) {
|
|
DirectParserASTContentTopLevelDeclarationEnd cls = entry.value;
|
|
|
|
// Simple "class hierarchy" to only work on TreeNodes.
|
|
if (goodNames.contains(entry.key)) {
|
|
// Cached good.
|
|
} else {
|
|
// Check if good.
|
|
String parent = getExtends(cls);
|
|
DirectParserASTContentTopLevelDeclarationEnd parentCls = classes[parent];
|
|
List<String> allParents = [parent];
|
|
while (
|
|
parent != null && parentCls != null && !goodNames.contains(parent)) {
|
|
parent = getExtends(parentCls);
|
|
allParents.add(parent);
|
|
parentCls = classes[parent];
|
|
}
|
|
if (goodNames.contains(parent)) {
|
|
goodNames.addAll(allParents);
|
|
} else {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
DirectParserASTContentClassDeclarationEnd classDeclaration =
|
|
cls.getClassDeclaration();
|
|
DirectParserASTContentClassOrMixinBodyEnd classOrMixinBody =
|
|
classDeclaration.getClassOrMixinBody();
|
|
|
|
Set<String> namedClassConstructors = {};
|
|
Set<String> namedFields = {};
|
|
for (DirectParserASTContentMemberEnd member
|
|
in classOrMixinBody.getMembers()) {
|
|
if (member.isClassConstructor()) {
|
|
DirectParserASTContentClassConstructorEnd constructor =
|
|
member.getClassConstructor();
|
|
Token nameToken = constructor.beginToken;
|
|
// String name = nameToken.lexeme;
|
|
if (nameToken.next.lexeme == ".") {
|
|
nameToken = nameToken.next.next;
|
|
// name += ".$nameToken";
|
|
namedClassConstructors.add(nameToken.lexeme);
|
|
}
|
|
if (nameToken.next.lexeme == ".") {
|
|
throw "Unexpected";
|
|
}
|
|
} else if (member.isClassFields()) {
|
|
DirectParserASTContentClassFieldsEnd classFields =
|
|
member.getClassFields();
|
|
Token identifierToken = classFields.getFieldIdentifiers().single.token;
|
|
String identifier = identifierToken.toString();
|
|
namedFields.add(identifier);
|
|
}
|
|
}
|
|
|
|
// If there isn't a `frozen` field in `TreeNode` we insert one.
|
|
if (entry.key == "TreeNode" && !namedFields.contains("frozen")) {
|
|
Token classBraceToken = classOrMixinBody.beginToken;
|
|
assert(classBraceToken.lexeme == "{");
|
|
replacements[classBraceToken] = new Replacement(
|
|
classBraceToken, classBraceToken, "{\n bool frozen = false;");
|
|
}
|
|
|
|
for (DirectParserASTContentMemberEnd member
|
|
in classOrMixinBody.getMembers()) {
|
|
if (member.isClassConstructor()) {
|
|
processConstructor(
|
|
member, replacements, namedClassConstructors, namedFields);
|
|
} else if (member.isClassFields()) {
|
|
processField(member, entry, replacements);
|
|
}
|
|
}
|
|
}
|
|
Token token = ast.getBegin().token;
|
|
|
|
int endOfLast = token.end;
|
|
while (token != null) {
|
|
CommentToken comment = token.precedingComments;
|
|
while (comment != null) {
|
|
if (comment.offset > endOfLast) {
|
|
for (int i = endOfLast; i < comment.offset; i++) {
|
|
int byte = bytes[i];
|
|
stdout.writeCharCode(byte);
|
|
}
|
|
}
|
|
|
|
stdout.write(comment.value());
|
|
endOfLast = comment.end;
|
|
comment = comment.next;
|
|
}
|
|
|
|
if (token.isEof) break;
|
|
if (token.offset > endOfLast) {
|
|
for (int i = endOfLast; i < token.offset; i++) {
|
|
int byte = bytes[i];
|
|
stdout.writeCharCode(byte);
|
|
}
|
|
}
|
|
|
|
Replacement replacement = replacements[token];
|
|
if (replacement != null) {
|
|
stdout.write(replacement.replacement);
|
|
token = replacement.endToken;
|
|
} else {
|
|
stdout.write(token.lexeme);
|
|
}
|
|
endOfLast = token.end;
|
|
token = token.next;
|
|
}
|
|
}
|
|
|
|
void processField(
|
|
DirectParserASTContentMemberEnd member,
|
|
MapEntry<String, DirectParserASTContentTopLevelDeclarationEnd> entry,
|
|
Map<Token, Replacement> replacements) {
|
|
DirectParserASTContentClassFieldsEnd classFields = member.getClassFields();
|
|
|
|
if (classFields.count != 1) {
|
|
throw "Notice ${classFields.count}";
|
|
}
|
|
|
|
Token identifierToken = classFields.getFieldIdentifiers().single.token;
|
|
String identifier = identifierToken.toString();
|
|
|
|
if (identifier == "frozen" && entry.key == "TreeNode") return;
|
|
|
|
if (classFields.staticToken != null) {
|
|
return;
|
|
}
|
|
bool isFinal = false;
|
|
if (classFields.varFinalOrConst?.toString() == "final") {
|
|
isFinal = true;
|
|
}
|
|
|
|
DirectParserASTContentTypeHandle type = classFields.getFirstType();
|
|
String typeString = "dynamic";
|
|
if (type != null) {
|
|
Token token = type.beginToken;
|
|
typeString = "";
|
|
while (token != identifierToken) {
|
|
typeString += " ${token.lexeme}";
|
|
token = token.next;
|
|
}
|
|
typeString = typeString.trim();
|
|
}
|
|
|
|
DirectParserASTContentFieldInitializerEnd initializer =
|
|
classFields.getFieldInitializer();
|
|
String initializerString = "";
|
|
if (initializer != null) {
|
|
Token token = initializer.assignment;
|
|
Token endToken = initializer.token;
|
|
while (token != endToken) {
|
|
initializerString += " ${token.lexeme}";
|
|
token = token.next;
|
|
}
|
|
initializerString = initializerString.trim();
|
|
}
|
|
|
|
Token beginToken = classFields.beginToken;
|
|
Token endToken = classFields.endToken;
|
|
assert(beginToken != null);
|
|
assert(endToken != null);
|
|
|
|
String frozenCheckCode =
|
|
"""if (frozen) throw "Trying to modify frozen node!";""";
|
|
|
|
if (identifier == "parent" && entry.key == "TreeNode") {
|
|
// We update the parent for libraries for instance all the time (e.g.
|
|
// when we link).
|
|
frozenCheckCode = "";
|
|
} else if (identifier == "transformerFlags" && entry.key == "Member") {
|
|
// The verifier changes this for all libraries
|
|
// (and then change it back again).
|
|
frozenCheckCode = "";
|
|
} else if (identifier == "initializer" && entry.key == "Field") {
|
|
// The constant evaluator does some stuff here. Only allow that
|
|
// when it's basically a no-op though!
|
|
frozenCheckCode = """
|
|
if (frozen) {
|
|
if (_initializer is ConstantExpression && newValue is ConstantExpression) {
|
|
if ((_initializer as ConstantExpression).constant == newValue.constant) {
|
|
_initializer = newValue;
|
|
return;
|
|
}
|
|
}
|
|
throw "Trying to modify frozen node!";
|
|
}""";
|
|
}
|
|
|
|
if (!isFinal) {
|
|
replacements[beginToken] = new Replacement(beginToken, endToken, """
|
|
$typeString _$identifier$initializerString;
|
|
$typeString get $identifier => _$identifier;
|
|
void set $identifier($typeString newValue) {
|
|
$frozenCheckCode
|
|
_$identifier = newValue;
|
|
}""");
|
|
} else {
|
|
// Don't create setter for final field.
|
|
// TODO: Possibly wrap a list for instance of a non-writable one.
|
|
replacements[beginToken] = new Replacement(beginToken, endToken, """
|
|
final $typeString _$identifier$initializerString;
|
|
$typeString get $identifier => _$identifier;""");
|
|
}
|
|
}
|
|
|
|
void processConstructor(
|
|
DirectParserASTContentMemberEnd member,
|
|
Map<Token, Replacement> replacements,
|
|
Set<String> namedClassConstructors,
|
|
Set<String> namedFields) {
|
|
DirectParserASTContentClassConstructorEnd constructor =
|
|
member.getClassConstructor();
|
|
DirectParserASTContentFormalParametersEnd formalParameters =
|
|
constructor.getFormalParameters();
|
|
List<DirectParserASTContentFormalParameterEnd> parameters =
|
|
formalParameters.getFormalParameters();
|
|
|
|
for (DirectParserASTContentFormalParameterEnd parameter in parameters) {
|
|
Token token = parameter.getBegin().token;
|
|
if (token?.lexeme != "this") {
|
|
continue;
|
|
}
|
|
// Here `this.foo` can just be replace with `this._foo`.
|
|
Token afterDot = token.next.next;
|
|
replacements[afterDot] = new Replacement(afterDot, afterDot, "_$afterDot");
|
|
}
|
|
|
|
DirectParserASTContentOptionalFormalParametersEnd optionalFormalParameters =
|
|
formalParameters.getOptionalFormalParameters();
|
|
Set<String> addInitializers = {};
|
|
if (optionalFormalParameters != null) {
|
|
List<DirectParserASTContentFormalParameterEnd> parameters =
|
|
optionalFormalParameters.getFormalParameters();
|
|
|
|
for (DirectParserASTContentFormalParameterEnd parameter in parameters) {
|
|
Token token = parameter.getBegin().token;
|
|
if (token?.lexeme != "this") {
|
|
continue;
|
|
}
|
|
// Here `this.foo` can't just be replace with `this._foo` as it is
|
|
// (possibly) named and we can't use private stuff in named.
|
|
// Instead we replace it with `dynamic foo` here and add an
|
|
// initializer `this._foo = foo`.
|
|
Token afterDot = token.next.next;
|
|
addInitializers.add(afterDot.lexeme);
|
|
replacements[token] = new Replacement(token, token.next, "dynamic ");
|
|
}
|
|
}
|
|
|
|
DirectParserASTContentInitializersEnd initializers =
|
|
constructor.getInitializers();
|
|
|
|
// First patch up any existing initializers.
|
|
if (initializers != null) {
|
|
List<DirectParserASTContentInitializerEnd> actualInitializers =
|
|
initializers.getInitializers();
|
|
for (DirectParserASTContentInitializerEnd initializer
|
|
in actualInitializers) {
|
|
Token token = initializer.getBegin().token;
|
|
// This is only afterDot if there's a dot --- which (probably) is
|
|
// only there if there's a `this`.
|
|
Token afterDot = token.next.next;
|
|
|
|
// We need to check it's not a redirecting call!
|
|
// TODO(jensj): Handle redirects like this:
|
|
// class C {
|
|
// C();
|
|
// C.redirect() : this();
|
|
// }
|
|
if (token.lexeme == "this" &&
|
|
namedClassConstructors.contains(afterDot.lexeme)) {
|
|
// Redirect!
|
|
continue;
|
|
}
|
|
|
|
if (token.lexeme == "this") {
|
|
// Here `this.foo` can just be replace with `this._foo`.
|
|
assert(namedFields.contains(afterDot.lexeme));
|
|
replacements[afterDot] =
|
|
new Replacement(afterDot, afterDot, "_$afterDot");
|
|
} else if (token.lexeme == "super") {
|
|
// Don't try to patch this one.
|
|
} else if (token.lexeme == "assert") {
|
|
List<DirectParserASTContentIdentifierHandle> identifiers = initializer
|
|
.recursivelyFind<DirectParserASTContentIdentifierHandle>();
|
|
for (Token token in identifiers.map((e) => e.token)) {
|
|
if (namedFields.contains(token.lexeme)) {
|
|
replacements[token] = new Replacement(token, token, "_$token");
|
|
}
|
|
}
|
|
} else {
|
|
assert(namedFields.contains(token.lexeme),
|
|
"${token.lexeme} isn't a known field among ${namedFields}");
|
|
replacements[token] = new Replacement(token, token, "_$token");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Then add any new ones.
|
|
if (addInitializers.isNotEmpty && initializers == null) {
|
|
// No initializers => Fake one by inserting `:` and all `_foo = foo`
|
|
// entries.
|
|
Token endToken = formalParameters.endToken;
|
|
String initializerString =
|
|
addInitializers.map((e) => "this._$e = $e").join(",\n");
|
|
replacements[endToken] =
|
|
new Replacement(endToken, endToken, ") : $initializerString");
|
|
} else if (addInitializers.isNotEmpty) {
|
|
// Add to existing initializer list. We add them as the first one(s)
|
|
// so we don't have to insert before the potential super call.
|
|
DirectParserASTContentInitializersBegin firstOne = initializers.getBegin();
|
|
Token colon = firstOne.token;
|
|
assert(colon.lexeme == ":");
|
|
String initializerString =
|
|
addInitializers.map((e) => "this._$e = $e").join(", ");
|
|
replacements[colon] =
|
|
new Replacement(colon, colon, ": $initializerString,");
|
|
}
|
|
|
|
// If there are anything in addInitializers we need to patch
|
|
// up the body too -- if we replace `{this.foo}` with `{dynamic foo}`
|
|
// and the body says `foo = 42;` before that would change the field,
|
|
// now it will change the parameter value. We must patch up all usages
|
|
// - even reads to work on things like
|
|
// class C {
|
|
// int field1;
|
|
// int field2;
|
|
// C(this.field1) : field2 = field1 + 1;
|
|
// }
|
|
if (addInitializers.isNotEmpty) {
|
|
DirectParserASTContentBlockFunctionBodyEnd blockFunctionBody =
|
|
constructor.getBlockFunctionBody();
|
|
if (blockFunctionBody != null) {
|
|
List<DirectParserASTContentIdentifierHandle> identifiers =
|
|
blockFunctionBody
|
|
.recursivelyFind<DirectParserASTContentIdentifierHandle>();
|
|
for (DirectParserASTContentIdentifierHandle identifier in identifiers) {
|
|
Token token = identifier.token;
|
|
IdentifierContext context = identifier.context;
|
|
if (namedFields.contains(token.lexeme) &&
|
|
addInitializers.contains(token.lexeme)) {
|
|
// For now naively assume that if it's a continuation it says
|
|
// `this.`
|
|
if (!context.isContinuation) {
|
|
replacements[token] = new Replacement(token, token, "_$token");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class Replacement {
|
|
final Token beginToken;
|
|
final Token endToken;
|
|
final String replacement;
|
|
|
|
Replacement(this.beginToken, this.endToken, this.replacement);
|
|
}
|
|
|
|
String getExtends(DirectParserASTContentTopLevelDeclarationEnd cls) {
|
|
DirectParserASTContentClassDeclarationEnd classDeclaration =
|
|
cls.getClassDeclaration();
|
|
|
|
DirectParserASTContentClassExtendsHandle classExtends =
|
|
classDeclaration.getClassExtends();
|
|
Token extendsKeyword = classExtends.extendsKeyword;
|
|
if (extendsKeyword == null) {
|
|
return null;
|
|
} else {
|
|
return extendsKeyword.next.toString();
|
|
}
|
|
}
|
|
|
|
void debugDumpNode(DirectParserASTContent node) {
|
|
node.children.forEach((element) {
|
|
print("${element.what} (${element.deprecatedArguments}) "
|
|
"(${element.children})");
|
|
});
|
|
}
|
|
|
|
void debugDumpNodeRecursively(DirectParserASTContent node,
|
|
{String indent = ""}) {
|
|
print("$indent${node.what} (${node.deprecatedArguments})");
|
|
if (node.children == null) return;
|
|
node.children.forEach((element) {
|
|
print("$indent${element.what} (${element.deprecatedArguments})");
|
|
if (element.children != null) {
|
|
element.children.forEach((element) {
|
|
debugDumpNodeRecursively(element, indent: " $indent");
|
|
});
|
|
}
|
|
});
|
|
}
|