blob: 005b7fa4179227dc7148b7d4be393183dc875e2f [file] [log] [blame]
// 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:front_end/src/api_prototype/front_end.dart';
import 'package:front_end/src/api_unstable/ddc.dart';
import 'package:front_end/src/compute_platform_binaries_location.dart';
import 'package:front_end/src/kernel_generator_impl.dart';
import 'package:kernel/ast.dart';
import 'package:kernel/class_hierarchy.dart';
import 'package:kernel/core_types.dart';
import 'package:kernel/src/printer.dart';
import 'package:kernel/target/targets.dart';
import 'package:kernel/type_algebra.dart';
import 'package:kernel/type_environment.dart';
import 'package:vm/target/vm.dart';
final Uri astLibraryUri = Uri.parse('package:kernel/ast.dart');
final Uri canonicalNameLibraryUri =
Uri.parse('package:kernel/canonical_name.dart');
Uri computePackageConfig(Uri repoDir) =>
repoDir.resolve('.dart_tool/package_config.json');
/// Map from names of node classes that declares nominal entities that are _not_
/// referenced through a [Reference] to their identifying name, if any.
///
/// For these classes, [_fieldRuleMap] must defined whether fields of these
/// types should be consider declarations or references to the declarations.
///
/// If the identifying name is non-null, this is used to determine the
/// nominality. For instance the name of a variable declaration is taking as
/// defining its identity.
const Map<String, String?> _declarativeClassesNames = const {
'VariableDeclaration': 'name',
'TypeParameter': 'name',
'LabeledStatement': null,
'SwitchCase': null,
};
/// Names of non-node, non-enum classes that should be treated as atomic
/// values.
const Set<String> _utilityClassesAsValues = const {
'Version',
};
/// Names of subclasses of [Node] that do _not_ have `visitX` or `defaultX`
/// methods.
const Set<String> _classesWithoutVisitMethods = const {
'InvocationExpression',
'InstanceInvocationExpression',
'NamedNode',
'PrimitiveConstant',
};
/// Names of inner [Node] classes that are used as interfaces for (generally)
/// interchangeable classes.
///
/// For instance, when [Expression] is used as the field type, any subtype of
/// [Expression] can be used to populate the field.
const Set<String> _interchangeableClasses = const {
'Member',
'Statement',
'Expression',
'Constant',
'DartType',
'Initializer',
};
/// Names of subclasses of [NamedNode] that do _not_ have `visitXReference` or
/// `defaultXReference` methods.
const Set<String> _classesWithoutVisitReference = const {
'NamedNode',
'Library',
'PrimitiveConstant',
};
/// Map of [FieldRule]s. These consist of a map for each class name, with
/// `null` used for "any class". For each class, a map from field names to
/// [FieldRule] define exceptions to how a field should be treated.
///
/// If a field name maps to `null`, the field is not included in the [AstModel].
const Map<String?, Map<String, FieldRule?>> _fieldRuleMap = {
null: {
'hashCode': null,
'parent': null,
},
'Component': {
'root': null,
'_mainMethodName': FieldRule(name: 'mainMethodName'),
'_mode': FieldRule(name: 'mode'),
},
'Library': {
'_languageVersion': FieldRule(name: 'languageVersion'),
'_libraryId': null,
'_classes': FieldRule(name: 'classes'),
'_typedefs': FieldRule(name: 'typedefs'),
'_extensions': FieldRule(name: 'extensions'),
'_fields': FieldRule(name: 'fields'),
'_procedures': FieldRule(name: 'procedures'),
},
'Class': {
'typeParameters': FieldRule(isDeclaration: true),
'_constructorsView': null,
'_constructorsInternal': FieldRule(name: 'constructors'),
'_fieldsView': null,
'_fieldsInternal': FieldRule(name: 'fields'),
'_proceduresView': null,
'_proceduresInternal': FieldRule(name: 'procedures'),
'_redirectingFactoriesView': null,
'_redirectingFactoriesInternal': FieldRule(name: 'redirectingFactories'),
'_onClause': null,
'lazyBuilder': null,
'dirty': null,
},
'Extension': {
'typeParameters': FieldRule(isDeclaration: true),
},
'Field': {
'reference': FieldRule(name: 'fieldReference'),
},
'TypeParameter': {
'_variance': FieldRule(name: 'variance'),
},
'FunctionNode': {
'_body': FieldRule(name: 'body'),
'typeParameters': FieldRule(isDeclaration: true),
'positionalParameters': FieldRule(isDeclaration: true),
'namedParameters': FieldRule(isDeclaration: true),
},
'Typedef': {
'typeParameters': FieldRule(isDeclaration: true),
},
'TypedefTearOff': {
'typeParameters': FieldRule(isDeclaration: true),
},
'TypedefTearOffConstant': {
'parameters': FieldRule(isDeclaration: true),
},
'LocalInitializer': {
'variable': FieldRule(isDeclaration: true),
},
'Let': {
'variable': FieldRule(isDeclaration: true),
},
'VariableGet': {
'variable': FieldRule(isDeclaration: false),
},
'VariableSet': {
'variable': FieldRule(isDeclaration: false),
},
'LocalFunctionInvocation': {
'variable': FieldRule(isDeclaration: false),
},
'BreakStatement': {
'target': FieldRule(isDeclaration: false),
},
'ForStatement': {
'variables': FieldRule(isDeclaration: true),
},
'ForInStatement': {
'variable': FieldRule(isDeclaration: true),
},
'SwitchStatement': {
'cases': FieldRule(isDeclaration: true),
},
'ContinueSwitchStatement': {
'target': FieldRule(isDeclaration: false),
},
'Catch': {
'exception': FieldRule(isDeclaration: true),
'stackTrace': FieldRule(isDeclaration: true),
},
'FunctionDeclaration': {
'variable': FieldRule(isDeclaration: true),
},
'FunctionType': {
'typeParameters': FieldRule(isDeclaration: true),
},
'TypeParameterType': {
'parameter': FieldRule(isDeclaration: false),
},
};
/// Data that determines exceptions to how fields are used.
class FieldRule {
/// If non-null, the field should be accessed by this name.
///
/// This is for instance used for private fields accessed through a public
/// getter.
final String? name;
/// For fields contain ast class of kind `AstClassKind.declarative`, this
/// value defines whether the field should be treated as a declaration or
/// a reference to the declaration.
final bool? isDeclaration;
const FieldRule({this.name, this.isDeclaration});
}
/// Return the [FieldRule] to use for the [field] in [AstClass].
FieldRule? getFieldRule(AstClass astClass, Field field) {
String name = field.name.text;
Map<String?, FieldRule?>? leafClassMap = _fieldRuleMap[astClass.name];
if (leafClassMap != null && leafClassMap.containsKey(name)) {
return leafClassMap[name];
}
Map<String?, FieldRule?>? enclosingClassMap =
_fieldRuleMap[field.enclosingClass!.name];
if (enclosingClassMap != null && enclosingClassMap.containsKey(name)) {
return enclosingClassMap[name];
}
Map<String?, FieldRule?>? defaultClassMap = _fieldRuleMap[null];
if (defaultClassMap != null && defaultClassMap.containsKey(name)) {
return defaultClassMap[name];
}
return new FieldRule(name: name);
}
/// Categorization of classes declared in 'package:kernel/ast.dart'.
enum AstClassKind {
/// The root [Node] class.
root,
/// An abstract node class that is a superclass of a public class. Most of
/// these have a corresponding `defaultX` visitor method.
///
/// For instance [Statement] and [Expression].
inner,
/// A concrete node class. These are the classes for which we have `visitX`
/// methods.
///
/// For instance [Procedure] and [VariableGet].
public,
/// A concrete node class that serves only as an implementation of public
/// node class.
///
/// For instance [PrivateName] and [PublicName] that implements the public
/// node [Name].
implementation,
/// A node class that serves as an interface.
///
/// For instance `FileUriNode`.
interface,
/// A named node class that declares a nominal entity that is used with a
/// reference.
named,
/// A node class that declares a nominal entity but used without reference.
///
/// For instance [VariableDeclaration] which introduces a new entity when it
/// occurs in a [Block] but is used as a reference when it occurs in a
/// [VariableGet].
declarative,
/// A none-node class that should be treated as a composite structure.
///
/// For instance [ConstantMapEntry] which is a tuple of a [Constant] key and
/// a [Constant] value.
utilityAsStructure,
/// A none-node class that should be treated as an atomic value.
///
/// For instance enum classes.
utilityAsValue,
}
class AstClass {
final Class node;
AstClassKind? _kind;
final String? declarativeName;
final bool isInterchangeable;
AstClass? superclass;
List<AstClass> interfaces = [];
List<AstClass> subclasses = [];
List<AstClass> subtypes = [];
Map<String, AstField> fields = {};
AstClass(this.node,
{this.superclass,
AstClassKind? kind,
this.declarativeName,
required this.isInterchangeable})
: _kind = kind {
if (superclass != null) {
superclass!.subclasses.add(this);
}
}
String get name => node.name;
AstClassKind get kind {
if (_kind == null) {
if (node.isAbstract) {
if (subclasses.isNotEmpty) {
if (subclasses.every(
(element) => element.kind == AstClassKind.implementation)) {
_kind = AstClassKind.public;
} else {
_kind = AstClassKind.inner;
}
} else {
_kind = AstClassKind.interface;
}
} else {
if (node.name.startsWith('_')) {
_kind = AstClassKind.implementation;
} else {
_kind = AstClassKind.public;
}
}
}
return _kind!;
}
/// Returns `true` if this class has a `visitX` or `defaultX` method.
///
/// This is only valid for subclass of [Node].
bool get hasVisitMethod {
switch (kind) {
case AstClassKind.root:
case AstClassKind.inner:
case AstClassKind.public:
case AstClassKind.named:
case AstClassKind.declarative:
return !_classesWithoutVisitMethods.contains(name);
case AstClassKind.implementation:
case AstClassKind.interface:
case AstClassKind.utilityAsStructure:
case AstClassKind.utilityAsValue:
return false;
}
}
/// Returns `true` if this class has a `visitXReference` or
/// `defaultXReference` method.
///
/// This is only valid for subclass of [NamedNode] or [Constant].
bool get hasVisitReferenceMethod {
switch (kind) {
case AstClassKind.root:
case AstClassKind.inner:
case AstClassKind.public:
case AstClassKind.named:
case AstClassKind.declarative:
return !_classesWithoutVisitReference.contains(name);
case AstClassKind.implementation:
case AstClassKind.interface:
case AstClassKind.utilityAsStructure:
case AstClassKind.utilityAsValue:
return false;
}
}
String dump([String indent = ""]) {
StringBuffer sb = new StringBuffer();
sb.writeln('${indent}${node.name} ($kind)');
for (AstField field in fields.values) {
sb.write(field.dump('${indent} '));
}
for (AstClass subclass in subclasses) {
sb.write(subclass.dump('${indent} '));
}
return sb.toString();
}
@override
String toString() => '${runtimeType}(${name})';
}
/// Categorization of field types.
enum AstFieldKind {
/// An atomic non-node value.
///
/// For instance an [int] value.
value,
/// A [Node] value that is part of the tree.
///
/// For instance an [Expression] value.
node,
/// A [Reference] value.
reference,
/// A reference to a declarative [Node].
///
/// For instance the reference to [VariableDeclaration] in [VariableGet].
use,
/// A list of values.
list,
/// A set of values.
set,
/// A map of values.
map,
/// A non-node composite value.
///
/// For instance a [ConstantMapEntry] that must be treat as a tuple of
/// a [Constant] key and a [Constant] value.
utility,
}
/// Structural description of a field type.
class FieldType {
final DartType type;
final AstFieldKind kind;
FieldType(this.type, this.kind);
@override
String toString() => 'FieldType($type,$kind)';
}
class ListFieldType extends FieldType {
final FieldType elementType;
ListFieldType(DartType type, this.elementType)
: super(type, AstFieldKind.list);
@override
String toString() => 'ListFieldType($type,$elementType)';
}
class SetFieldType extends FieldType {
final FieldType elementType;
SetFieldType(DartType type, this.elementType) : super(type, AstFieldKind.set);
@override
String toString() => 'SetFieldType($type,$elementType)';
}
class MapFieldType extends FieldType {
final FieldType keyType;
final FieldType valueType;
MapFieldType(DartType type, this.keyType, this.valueType)
: super(type, AstFieldKind.map);
@override
String toString() => 'MapFieldType($type,$keyType,$valueType)';
}
class UtilityFieldType extends FieldType {
final AstClass astClass;
UtilityFieldType(DartType type, this.astClass)
: super(type, AstFieldKind.utility);
@override
String toString() => 'UtilityFieldType($type,$astClass)';
}
class AstField {
final AstClass astClass;
final Field node;
final String name;
final FieldType type;
final AstField? parentField;
AstField(this.astClass, this.node, this.name, this.type, this.parentField);
String dump([String indent = ""]) {
StringBuffer sb = new StringBuffer();
sb.writeln('$indent$name $type');
return sb.toString();
}
@override
String toString() => '${runtimeType}(${name})';
}
class AstModel {
final AstClass nodeClass;
final AstClass namedNodeClass;
final AstClass constantClass;
AstModel(this.nodeClass, this.namedNodeClass, this.constantClass);
/// Returns an [Iterable] for all declarative [Node] classes in the AST model.
Iterable<AstClass> get declarativeClasses {
return classes.where((cls) => cls.kind == AstClassKind.declarative);
}
/// Returns an [Iterable] for all [Node] (sub)classes in the AST model.
Iterable<AstClass> get classes sync* {
Iterable<AstClass> visitClass(AstClass cls) sync* {
yield cls;
for (AstClass subclass in cls.subclasses) {
yield* visitClass(subclass);
}
}
yield* visitClass(nodeClass);
}
}
/// Computes the [AstModel] from 'package:kernel/ast' using [repoDir] to locate
/// the package config file.
///
/// If [printDump] is `true`, a dump of the model printed to stdout.
Future<AstModel> deriveAstModel(Uri repoDir, {bool printDump: false}) async {
bool errorsFound = false;
CompilerOptions options = new CompilerOptions();
options.sdkRoot = computePlatformBinariesLocation(forceBuildDir: true);
options.compileSdk = true;
options.target = new VmTarget(new TargetFlags());
options.librariesSpecificationUri = repoDir.resolve("sdk/lib/libraries.json");
options.environmentDefines = const {};
options.packagesFileUri = computePackageConfig(repoDir);
options.onDiagnostic = (DiagnosticMessage message) {
printDiagnosticMessage(message, print);
if (message.severity == Severity.error) {
errorsFound = true;
}
};
InternalCompilerResult compilerResult = (await kernelForProgramInternal(
astLibraryUri, options,
retainDataForTesting: true,
requireMain: false)) as InternalCompilerResult;
if (errorsFound) {
throw 'Errors found';
}
ClassHierarchy classHierarchy = compilerResult.classHierarchy!;
CoreTypes coreTypes = compilerResult.coreTypes!;
TypeEnvironment typeEnvironment =
new TypeEnvironment(coreTypes, classHierarchy);
Library astLibrary = compilerResult.component!.libraries
.singleWhere((library) => library.importUri == astLibraryUri);
void reportError(String message) {
print(message);
errorsFound = true;
}
Map<String, String?> declarativeClassesNames = {..._declarativeClassesNames};
Set<String> classesWithoutVisitMethods = _classesWithoutVisitMethods.toSet();
Set<String> classesWithoutVisitReference =
_classesWithoutVisitReference.toSet();
Map<String?, Map<String, FieldRule?>> fieldRuleMap = {..._fieldRuleMap};
Map<String, FieldRule?> nullFieldRules = {...?fieldRuleMap.remove(null)};
Set<String> interchangeableClasses = _interchangeableClasses.toSet();
for (Class cls in astLibrary.classes) {
declarativeClassesNames.remove(cls.name);
classesWithoutVisitMethods.remove(cls.name);
classesWithoutVisitReference.remove(cls.name);
interchangeableClasses.remove(cls.name);
Map<String, FieldRule?> fieldRules = {...?fieldRuleMap.remove(cls.name)};
Set<String> renames = {};
Class? parent = cls;
while (parent != null && parent.enclosingLibrary == astLibrary) {
for (Field field in parent.fields) {
bool hasFieldRule = fieldRules.containsKey(field.name.text);
FieldRule? fieldRule = fieldRules.remove(field.name.text);
if (fieldRule != null) {
if (fieldRule.name != null) {
renames.add(fieldRule.name!);
}
}
if (!hasFieldRule) {
if (!cls.isEnum && !field.isStatic && field.name.isPrivate) {
reportError(
'Private field `${field.name.text}` in ${parent.name} must '
'have a field rule.');
}
}
if (nullFieldRules.containsKey(field.name.text)) {
FieldRule? nullFieldRule = nullFieldRules.remove(field.name.text);
if (nullFieldRule != null) {
reportError('Only `null` is allowed for class `null`.');
}
}
}
parent = parent.superclass;
}
for (Procedure procedure in cls.procedures) {
renames.remove(procedure.name.text);
}
if (renames.isNotEmpty) {
reportError('Unknown procedure(s) for field redirections in '
'${cls.name}: ${renames}');
}
if (fieldRules.isNotEmpty) {
reportError(
'Unknown field(s) for rules in ${cls.name}: ${fieldRules.keys}');
}
}
if (declarativeClassesNames.isNotEmpty) {
reportError('Unknown declarative classes: ${declarativeClassesNames}');
}
if (classesWithoutVisitMethods.isNotEmpty) {
reportError('Unknown classes without visit methods: '
'${classesWithoutVisitMethods}');
}
if (classesWithoutVisitReference.isNotEmpty) {
reportError('Unknown classes without visit reference methods: '
'${classesWithoutVisitReference}');
}
if (interchangeableClasses.isNotEmpty) {
reportError('Unknown interchangeable classes: '
'${interchangeableClasses}');
}
if (fieldRuleMap.isNotEmpty) {
reportError('Unknown classes with field rules: ${fieldRuleMap.keys}');
}
Class classNode = astLibrary.classes.singleWhere((cls) => cls.name == 'Node');
DartType nullableNodeType =
classNode.getThisType(coreTypes, Nullability.nullable);
Class classNamedNode =
astLibrary.classes.singleWhere((cls) => cls.name == 'NamedNode');
Class classConstant =
astLibrary.classes.singleWhere((cls) => cls.name == 'Constant');
Library canonicalNameLibrary = compilerResult.component!.libraries
.singleWhere((library) => library.importUri == canonicalNameLibraryUri);
Class referenceClass = canonicalNameLibrary.classes
.singleWhere((cls) => cls.name == 'Reference');
DartType nullableReferenceType =
referenceClass.getThisType(coreTypes, Nullability.nullable);
Set<Class> declarativeClasses = {};
Set<DartType> declarativeTypes = {};
for (String name in _declarativeClassesNames.keys) {
Class cls = astLibrary.classes.singleWhere((cls) => cls.name == name);
declarativeClasses.add(cls);
declarativeTypes.add(cls.getThisType(coreTypes, Nullability.nullable));
}
Map<Class, AstClass> classMap = {};
/// Computes the [AstClass] corresponding to [node] if [node] is declared in
/// 'package:kernel/ast.dart'.
AstClass? computeAstClass(Class? node) {
if (node == null) return null;
if (node.enclosingLibrary != astLibrary) return null;
AstClass? astClass = classMap[node];
if (astClass == null) {
bool isInterchangeable = _interchangeableClasses.contains(node.name);
if (node == classNode) {
astClass = new AstClass(node,
kind: AstClassKind.root, isInterchangeable: isInterchangeable);
} else if (classHierarchy.isSubtypeOf(node, classNode)) {
AstClass? superclass = computeAstClass(node.superclass);
AstClassKind? kind;
String? declarativeName;
if (!node.isAbstract &&
classHierarchy.isSubtypeOf(node, classNamedNode)) {
kind = AstClassKind.named;
} else if (declarativeClasses.contains(node)) {
kind = AstClassKind.declarative;
declarativeName = _declarativeClassesNames[node.name];
}
astClass = new AstClass(node,
superclass: superclass,
kind: kind,
declarativeName: declarativeName,
isInterchangeable: isInterchangeable);
for (Supertype supertype in node.implementedTypes) {
AstClass? astSupertype = computeAstClass(supertype.classNode);
if (astSupertype != null) {
astClass.interfaces.add(astSupertype);
astSupertype.subtypes.add(astClass);
}
}
} else if (node.isEnum || _utilityClassesAsValues.contains(node.name)) {
astClass = new AstClass(node,
kind: AstClassKind.utilityAsValue,
isInterchangeable: isInterchangeable);
} else {
AstClass? superclass = computeAstClass(node.superclass);
astClass = new AstClass(node,
superclass: superclass,
kind: AstClassKind.utilityAsStructure,
isInterchangeable: isInterchangeable);
}
classMap[node] = astClass;
}
return astClass;
}
for (Class cls in astLibrary.classes) {
computeAstClass(cls);
}
Set<AstClass> hasComputedFields = {};
void computeAstFields(AstClass astClass) {
if (!hasComputedFields.add(astClass)) return;
void computeAstField(Field field, Substitution substitution) {
if (field.isStatic) {
return;
}
FieldRule? rule = getFieldRule(astClass, field);
if (rule == null) {
return;
}
DartType type = substitution.substituteType(field.type);
FieldType computeFieldType(DartType type) {
bool isDeclarativeType = false;
for (DartType declarativeType in declarativeTypes) {
if (type is InterfaceType &&
typeEnvironment.isSubtypeOf(
type, declarativeType, SubtypeCheckMode.withNullabilities)) {
isDeclarativeType = true;
break;
}
}
if (isDeclarativeType) {
if (rule.isDeclaration == null) {
reportError(
"No field rule for '${field.name}' in ${astClass.name}.\n"
"The field type contains the declarative type "
"'${type.toText(defaultAstTextStrategy)}' "
"and a rule must therefore specify "
"whether this constitutes declarative or referential use.");
}
if (!rule.isDeclaration!) {
return new FieldType(type, AstFieldKind.use);
}
}
if (type is InterfaceType &&
typeEnvironment.isSubtypeOf(type, coreTypes.listNullableRawType,
SubtypeCheckMode.withNullabilities)) {
DartType elementType = typeEnvironment
.getTypeArgumentsAsInstanceOf(type, coreTypes.listClass)!
.single;
return new ListFieldType(type, computeFieldType(elementType));
} else if (type is InterfaceType &&
typeEnvironment.isSubtypeOf(type, coreTypes.setNullableRawType,
SubtypeCheckMode.withNullabilities)) {
DartType elementType = typeEnvironment
.getTypeArgumentsAsInstanceOf(type, coreTypes.setClass)!
.single;
return new SetFieldType(type, computeFieldType(elementType));
} else if (type is InterfaceType &&
typeEnvironment.isSubtypeOf(type, coreTypes.mapNullableRawType,
SubtypeCheckMode.withNullabilities)) {
List<DartType> typeArguments = typeEnvironment
.getTypeArgumentsAsInstanceOf(type, coreTypes.mapClass)!;
return new MapFieldType(type, computeFieldType(typeArguments[0]),
computeFieldType(typeArguments[1]));
} else if (type is InterfaceType &&
typeEnvironment.isSubtypeOf(
type, nullableNodeType, SubtypeCheckMode.withNullabilities)) {
return new FieldType(type, AstFieldKind.node);
} else if (type is InterfaceType &&
typeEnvironment.isSubtypeOf(type, nullableReferenceType,
SubtypeCheckMode.withNullabilities)) {
return new FieldType(type, AstFieldKind.reference);
} else {
if (type is InterfaceType) {
AstClass? astClass = classMap[type.classNode];
if (astClass != null &&
astClass.kind == AstClassKind.utilityAsStructure) {
return new UtilityFieldType(type, astClass);
}
}
return new FieldType(type, AstFieldKind.value);
}
}
FieldType? fieldType = computeFieldType(type);
String name = rule.name ?? field.name.text;
astClass.fields[name] = new AstField(
astClass, field, name, fieldType, astClass.superclass?.fields[name]);
}
AstClass? parent = astClass;
Substitution substitution = Substitution.empty;
while (parent != null) {
for (Field field in parent.node.fields) {
computeAstField(field, substitution);
}
parent = parent.superclass;
if (parent != null) {
substitution = Substitution.fromSupertype(
classHierarchy.getClassAsInstanceOf(astClass.node, parent.node)!);
}
}
}
for (AstClass astClass in classMap.values) {
computeAstFields(astClass);
}
AstClass astClassNode = classMap[classNode]!;
AstClass astClassNamedNode = classMap[classNamedNode]!;
AstClass astClassConstant = classMap[classConstant]!;
if (printDump) {
print(classMap[classNode]!.dump());
for (AstClass astClass in classMap.values) {
if (astClass != astClassNode && astClass.superclass == null) {
print(astClass.dump());
}
}
}
if (errorsFound) {
throw 'Errors found';
}
return new AstModel(astClassNode, astClassNamedNode, astClassConstant);
}