Skip to content

Commit

Permalink
Add support for accessing field formal parameters in the initializer …
Browse files Browse the repository at this point in the history
…list of constructors

R=scheglov@google.com

Review URL: https://codereview.chromium.org/2335693002 .
  • Loading branch information
bwilkerson committed Sep 12, 2016
1 parent b5462ca commit a5ee525
Show file tree
Hide file tree
Showing 11 changed files with 150 additions and 22 deletions.
4 changes: 4 additions & 0 deletions pkg/analyzer/lib/src/context/context.dart
Expand Up @@ -291,6 +291,8 @@ class AnalysisContextImpl implements InternalAnalysisContext {
this._options.enableAssertInitializer !=
options.enableAssertInitializer ||
this._options.enableAssertMessage != options.enableAssertMessage ||
this._options.enableInitializingFormalAccess !=
options.enableInitializingFormalAccess ||
((options is AnalysisOptionsImpl)
? this._options.strongModeHints != options.strongModeHints
: false) ||
Expand Down Expand Up @@ -322,6 +324,8 @@ class AnalysisContextImpl implements InternalAnalysisContext {
this._options.enableAssertMessage = options.enableAssertMessage;
this._options.enableStrictCallChecks = options.enableStrictCallChecks;
this._options.enableAsync = options.enableAsync;
this._options.enableInitializingFormalAccess =
options.enableInitializingFormalAccess;
this._options.enableSuperMixins = options.enableSuperMixins;
this._options.enableTiming = options.enableTiming;
this._options.hint = options.hint;
Expand Down
5 changes: 2 additions & 3 deletions pkg/analyzer/lib/src/dart/constant/evaluation.dart
Expand Up @@ -654,10 +654,9 @@ class ConstantEvaluationEngine {
}
fieldMap[fieldName] = argumentValue;
}
} else {
String name = baseParameter.name;
parameterMap[name] = argumentValue;
}
String name = baseParameter.name;
parameterMap[name] = argumentValue;
}
}
ConstantVisitor initializerVisitor = new ConstantVisitor(
Expand Down
24 changes: 24 additions & 0 deletions pkg/analyzer/lib/src/dart/resolver/scope.dart
Expand Up @@ -112,6 +112,30 @@ class ClassScope extends EnclosedScope {
}
}

/**
* The scope defined for the initializers in a constructor.
*/
class ConstructorInitializerScope extends EnclosedScope {
/**
* Initialize a newly created scope, enclosed within the [enclosingScope].
*/
ConstructorInitializerScope(Scope enclosingScope, ConstructorElement element)
: super(enclosingScope) {
_initializeFieldFormalParameters(element);
}

/**
* Initialize the local scope with all of the field formal parameters.
*/
void _initializeFieldFormalParameters(ConstructorElement element) {
for (ParameterElement parameter in element.parameters) {
if (parameter is FieldFormalParameterElement) {
define(parameter);
}
}
}
}

/**
* A scope that is lexically enclosed in another scope.
*/
Expand Down
10 changes: 10 additions & 0 deletions pkg/analyzer/lib/src/generated/engine.dart
Expand Up @@ -1076,6 +1076,12 @@ abstract class AnalysisOptions {
*/
bool get enableGenericMethods => null;

/**
* Return `true` if access to field formal parameters should be allowed in a
* constructor's initializer list.
*/
bool get enableInitializingFormalAccess;

/**
* Return `true` to enable the lazy compound assignment operators '&&=' and
* '||='.
Expand Down Expand Up @@ -1227,6 +1233,9 @@ class AnalysisOptionsImpl implements AnalysisOptions {
@override
bool enableGenericMethods = false;

@override
bool enableInitializingFormalAccess = false;

@override
bool enableLazyAssignmentOperators = false;

Expand Down Expand Up @@ -1325,6 +1334,7 @@ class AnalysisOptionsImpl implements AnalysisOptions {
enableAsync = options.enableAsync;
enableStrictCallChecks = options.enableStrictCallChecks;
enableGenericMethods = options.enableGenericMethods;
enableInitializingFormalAccess = options.enableInitializingFormalAccess;
enableSuperMixins = options.enableSuperMixins;
enableTiming = options.enableTiming;
generateImplicitErrors = options.generateImplicitErrors;
Expand Down
59 changes: 45 additions & 14 deletions pkg/analyzer/lib/src/generated/resolver.dart
Expand Up @@ -6348,6 +6348,12 @@ class ResolverVisitor extends ScopedVisitor {
@override
void visitConstructorDeclarationInScope(ConstructorDeclaration node) {
super.visitConstructorDeclarationInScope(node);
// Because of needing a different scope for the initializer list, the
// overridden implementation of this method cannot cause the visitNode
// method to be invoked. As a result, we have to hard-code using the
// element resolver and type analyzer to visit the constructor declaration.
node.accept(elementResolver);
node.accept(typeAnalyzer);
safelyVisitComment(node.documentationComment);
}

Expand Down Expand Up @@ -7743,6 +7749,12 @@ abstract class ScopedVisitor extends UnifyingAstVisitor<Object> {
*/
LabelScope labelScope;

/**
* A flag indicating whether to enable support for allowing access to field
* formal parameters in a constructor's initializer list.
*/
bool enableInitializingFormalAccess = false;

/**
* The class containing the AST nodes being visited,
* or `null` if we are not in the scope of a class.
Expand Down Expand Up @@ -7774,6 +7786,8 @@ abstract class ScopedVisitor extends UnifyingAstVisitor<Object> {
} else {
this.nameScope = nameScope;
}
enableInitializingFormalAccess =
definingLibrary.context.analysisOptions.enableInitializingFormalAccess;
}

/**
Expand Down Expand Up @@ -7910,23 +7924,40 @@ abstract class ScopedVisitor extends UnifyingAstVisitor<Object> {
@override
Object visitConstructorDeclaration(ConstructorDeclaration node) {
ConstructorElement constructorElement = node.element;
if (constructorElement == null) {
StringBuffer buffer = new StringBuffer();
buffer.write("Missing element for constructor ");
buffer.write(node.returnType.name);
if (node.name != null) {
buffer.write(".");
buffer.write(node.name.name);
}
buffer.write(" in ");
buffer.write(definingLibrary.source.fullName);
AnalysisEngine.instance.logger.logInformation(buffer.toString(),
new CaughtException(new AnalysisException(), null));
}
Scope outerScope = nameScope;
try {
if (constructorElement == null) {
StringBuffer buffer = new StringBuffer();
buffer.write("Missing element for constructor ");
buffer.write(node.returnType.name);
if (node.name != null) {
buffer.write(".");
buffer.write(node.name.name);
}
buffer.write(" in ");
buffer.write(definingLibrary.source.fullName);
AnalysisEngine.instance.logger.logInformation(buffer.toString(),
new CaughtException(new AnalysisException(), null));
} else {
if (constructorElement != null) {
nameScope = new FunctionScope(nameScope, constructorElement);
}
node.documentationComment?.accept(this);
node.metadata.accept(this);
node.returnType?.accept(this);
node.name?.accept(this);
node.parameters?.accept(this);
Scope functionScope = nameScope;
try {
if (constructorElement != null && enableInitializingFormalAccess) {
nameScope =
new ConstructorInitializerScope(nameScope, constructorElement);
}
node.initializers.accept(this);
} finally {
nameScope = functionScope;
}
node.redirectedConstructor?.accept(this);
visitConstructorDeclarationInScope(node);
} finally {
nameScope = outerScope;
Expand All @@ -7935,7 +7966,7 @@ abstract class ScopedVisitor extends UnifyingAstVisitor<Object> {
}

void visitConstructorDeclarationInScope(ConstructorDeclaration node) {
super.visitConstructorDeclaration(node);
node.body?.accept(this);
}

@override
Expand Down
4 changes: 4 additions & 0 deletions pkg/analyzer/lib/src/task/options.dart
Expand Up @@ -48,6 +48,8 @@ class AnalyzerOptions {
static const String enableAssertInitializer = 'enableAssertInitializer';
static const String enableAsync = 'enableAsync';
static const String enableGenericMethods = 'enableGenericMethods';
static const String enableInitializingFormalAccess =
'enableInitializingFormalAccess';
static const String enableStrictCallChecks = 'enableStrictCallChecks';
static const String enableSuperMixins = 'enableSuperMixins';

Expand Down Expand Up @@ -566,6 +568,8 @@ class _OptionsProcessor {
options.enableAssertInitializer = boolValue;
} else if (feature == AnalyzerOptions.enableAsync) {
options.enableAsync = boolValue;
} else if (feature == AnalyzerOptions.enableInitializingFormalAccess) {
options.enableInitializingFormalAccess = boolValue;
} else if (feature == AnalyzerOptions.enableSuperMixins) {
options.enableSuperMixins = boolValue;
} else if (feature == AnalyzerOptions.enableGenericMethods) {
Expand Down
24 changes: 24 additions & 0 deletions pkg/analyzer/test/generated/compile_time_error_code_test.dart
Expand Up @@ -1800,6 +1800,17 @@ class A {
verify([source]);
}

void test_duplicateDefinition_parameters_inConstructor() {
Source source = addSource(r'''
class A {
int a;
A(int a, this.a);
}''');
computeLibrarySourceErrors(source);
assertErrors(source, [CompileTimeErrorCode.DUPLICATE_DEFINITION]);
verify([source]);
}

void test_duplicateDefinition_parameters_inFunctionTypeAlias() {
Source source = addSource(r'''
typedef F(int a, double a);
Expand Down Expand Up @@ -2152,6 +2163,18 @@ class B extends A {
verify([source]);
}

void test_fieldFormalParameter_assignedInInitializer() {
Source source = addSource(r'''
class A {
int x;
A(this.x) : x = 3 {}
}''');
computeLibrarySourceErrors(source);
assertErrors(source,
[CompileTimeErrorCode.FIELD_INITIALIZED_IN_PARAMETER_AND_INITIALIZER]);
verify([source]);
}

void test_fieldInitializedByMultipleInitializers() {
Source source = addSource(r'''
class A {
Expand Down Expand Up @@ -2334,6 +2357,7 @@ class A {
A(this.x, this.x) {}
}''');
computeLibrarySourceErrors(source);
// TODO(brianwilkerson) There should only be one error here.
assertErrors(source, [
CompileTimeErrorCode.DUPLICATE_DEFINITION,
CompileTimeErrorCode.FINAL_INITIALIZED_MULTIPLE_TIMES
Expand Down
20 changes: 19 additions & 1 deletion pkg/analyzer/test/generated/simple_resolver_test.dart
Expand Up @@ -656,11 +656,29 @@ class A {
}

void test_fieldFormalParameter() {
AnalysisOptionsImpl options = new AnalysisOptionsImpl();
options.enableInitializingFormalAccess = true;
resetWithOptions(options);
Source source = addSource(r'''
class A {
int x;
A(this.x) {}
int y;
A(this.x) : y = x {}
}''');
CompilationUnit unit =
analysisContext2.resolveCompilationUnit2(source, source);
ClassDeclaration classA = unit.declarations[0];
FieldDeclaration field = classA.members[0];
ConstructorDeclaration constructor = classA.members[2];
ParameterElement paramElement =
constructor.parameters.parameters[0].element;
expect(paramElement, new isInstanceOf<FieldFormalParameterElement>());
expect((paramElement as FieldFormalParameterElement).field,
field.fields.variables[0].element);
ConstructorFieldInitializer initializer = constructor.initializers[0];
SimpleIdentifier identifierX = initializer.expression;
expect(identifierX.staticElement, paramElement);

computeLibrarySourceErrors(source);
assertNoErrors(source);
verify([source]);
Expand Down
6 changes: 6 additions & 0 deletions pkg/analyzer_cli/lib/src/driver.dart
Expand Up @@ -284,6 +284,10 @@ class Driver implements CommandLineStarter {
if (options.disableHints != _previousOptions.disableHints) {
return false;
}
if (options.enableInitializingFormalAccess !=
_previousOptions.enableInitializingFormalAccess) {
return false;
}
if (options.enableStrictCallChecks !=
_previousOptions.enableStrictCallChecks) {
return false;
Expand Down Expand Up @@ -647,6 +651,8 @@ class Driver implements CommandLineStarter {
AnalysisOptionsImpl contextOptions = new AnalysisOptionsImpl();
contextOptions.trackCacheDependencies = false;
contextOptions.hint = !options.disableHints;
contextOptions.enableInitializingFormalAccess =
options.enableInitializingFormalAccess;
contextOptions.enableStrictCallChecks = options.enableStrictCallChecks;
contextOptions.enableSuperMixins = options.enableSuperMixins;
contextOptions.generateImplicitErrors = options.showPackageWarnings;
Expand Down
11 changes: 11 additions & 0 deletions pkg/analyzer_cli/lib/src/options.dart
Expand Up @@ -87,6 +87,10 @@ class CommandLineOptions {
/// Whether to display version information
final bool displayVersion;

/// A flag indicating whether access to field formal parameters should be
/// allowed in a constructor's initializer list.
final bool enableInitializingFormalAccess;

/// Whether to enable null-aware operators (DEP 9).
final bool enableNullAwareOperators;

Expand Down Expand Up @@ -181,6 +185,7 @@ class CommandLineOptions {
analysisOptionsFile = args['options'],
disableHints = args['no-hints'],
displayVersion = args['version'],
enableInitializingFormalAccess = args['initializing-formal-access'],
enableNullAwareOperators = args['enable-null-aware-operators'],
enableStrictCallChecks = args['enable-strict-call-checks'],
enableSuperMixins = args['supermixin'],
Expand Down Expand Up @@ -470,6 +475,12 @@ class CommandLineOptions {
defaultsTo: false,
negatable: false,
hide: true)
..addFlag('initializing-formal-access',
help:
'Enable support for allowing access to field formal parameters in a constructor\'s initializer list',
defaultsTo: false,
negatable: false,
hide: true)
..addFlag('supermixin',
help: 'Relax restrictions on mixins (DEP 34).',
defaultsTo: false,
Expand Down
5 changes: 1 addition & 4 deletions tests/language/language_analyzer2.status
Expand Up @@ -507,8 +507,5 @@ generic_methods_function_type_test: CompiletimeError # Issue 25868
generic_methods_type_expression_test: CompiletimeError # Issue 25868

# Experimental feature: Use initializing formals in initializers and constructor body.
initializing_formal_access_test: CompiletimeError # Issue 26658
initializing_formal_capture_test: CompiletimeError # Issue 26658
initializing_formal_final_test: CompiletimeError # Issue 26658
initializing_formal_promotion_test: StaticWarning # Issue 26658
initializing_formal_type_test: CompiletimeError # Issue 26658
initializing_formal_type_test: StaticWarning # Issue 26658

0 comments on commit a5ee525

Please sign in to comment.