Skip to content

Commit 83a2449

Browse files
author
John Messerly
committed
Infer setter parameter types in strong mode
This change also decouples setters and getters, which matches what DDC currently does. The benefit is they're inferred as independent methods, which allows code reuse between method/getter/setters. It also prevents ordering issues that might occur otherwise (i.e. if getter inference considers setter's type, and setter inference considers the getter's type). More context: dart-archive/dev_compiler#342 (comment) [email protected] Review URL: https://codereview.chromium.org//1359243003 .
1 parent 54282cc commit 83a2449

File tree

2 files changed

+139
-52
lines changed

2 files changed

+139
-52
lines changed

pkg/analyzer/lib/src/task/strong_mode.dart

+51-51
Original file line numberDiff line numberDiff line change
@@ -230,37 +230,6 @@ class InstanceMemberInferrer {
230230
: matchingParameter.type;
231231
}
232232

233-
/**
234-
* If the given [accessorElement] represents a non-synthetic instance getter
235-
* for which no return type was provided, infer the return type of the getter.
236-
*/
237-
void _inferAccessor(PropertyAccessorElement accessorElement) {
238-
if (!accessorElement.isSynthetic &&
239-
accessorElement.isGetter &&
240-
!accessorElement.isStatic &&
241-
accessorElement.hasImplicitReturnType) {
242-
List<ExecutableElement> overriddenGetters = inheritanceManager
243-
.lookupOverrides(
244-
accessorElement.enclosingElement, accessorElement.name);
245-
if (overriddenGetters.isNotEmpty && _onlyGetters(overriddenGetters)) {
246-
DartType newType = _computeReturnType(overriddenGetters);
247-
List<ExecutableElement> overriddenSetters = inheritanceManager
248-
.lookupOverrides(
249-
accessorElement.enclosingElement, accessorElement.name + '=');
250-
PropertyAccessorElement setter = (accessorElement.enclosingElement
251-
as ClassElement).getSetter(accessorElement.name);
252-
if (setter != null) {
253-
overriddenSetters.add(setter);
254-
}
255-
if (!_isCompatible(newType, overriddenSetters)) {
256-
newType = typeProvider.dynamicType;
257-
}
258-
setReturnType(accessorElement, newType);
259-
(accessorElement.variable as FieldElementImpl).type = newType;
260-
}
261-
}
262-
}
263-
264233
/**
265234
* Infer type information for all of the instance members in the given
266235
* [classElement].
@@ -290,8 +259,8 @@ class InstanceMemberInferrer {
290259
// Then infer the types for the members.
291260
//
292261
classElement.fields.forEach(_inferField);
293-
classElement.accessors.forEach(_inferAccessor);
294-
classElement.methods.forEach(_inferMethod);
262+
classElement.accessors.forEach(_inferExecutable);
263+
classElement.methods.forEach(_inferExecutable);
295264
classElement.hasBeenInferred = true;
296265
} finally {
297266
elementsBeingInferred.remove(classElement);
@@ -344,46 +313,81 @@ class InstanceMemberInferrer {
344313
}
345314

346315
/**
347-
* If the given [methodElement] represents a non-synthetic instance method
348-
* for which no return type was provided, infer the return type of the method.
316+
* If the given [element] represents a non-synthetic instance method,
317+
* getter or setter, infer the return type and any parameter type(s) where
318+
* they were not provided.
349319
*/
350-
void _inferMethod(MethodElement methodElement) {
351-
if (methodElement.isSynthetic || methodElement.isStatic) {
320+
void _inferExecutable(ExecutableElement element) {
321+
if (element.isSynthetic || element.isStatic) {
352322
return;
353323
}
354324
List<ExecutableElement> overriddenMethods = null;
355325
//
356326
// Infer the return type.
357327
//
358-
if (methodElement.hasImplicitReturnType) {
328+
if (element.hasImplicitReturnType) {
359329
overriddenMethods = inheritanceManager.lookupOverrides(
360-
methodElement.enclosingElement, methodElement.name);
361-
if (overriddenMethods.isEmpty || !_onlyMethods(overriddenMethods)) {
330+
element.enclosingElement, element.name);
331+
if (overriddenMethods.isEmpty ||
332+
!_allSameElementKind(element, overriddenMethods)) {
362333
return;
363334
}
364-
MethodElementImpl element = methodElement as MethodElementImpl;
365335
setReturnType(element, _computeReturnType(overriddenMethods));
336+
if (element is PropertyAccessorElement) {
337+
_updateSyntheticVariableType(element);
338+
}
366339
}
367340
//
368341
// Infer the parameter types.
369342
//
370-
List<ParameterElement> parameters = methodElement.parameters;
343+
List<ParameterElement> parameters = element.parameters;
371344
int length = parameters.length;
372345
for (int i = 0; i < length; ++i) {
373346
ParameterElement parameter = parameters[i];
374347
if (parameter is ParameterElementImpl && parameter.hasImplicitType) {
375348
if (overriddenMethods == null) {
376349
overriddenMethods = inheritanceManager.lookupOverrides(
377-
methodElement.enclosingElement, methodElement.name);
350+
element.enclosingElement, element.name);
378351
}
379-
if (overriddenMethods.isEmpty || !_onlyMethods(overriddenMethods)) {
352+
if (overriddenMethods.isEmpty ||
353+
!_allSameElementKind(element, overriddenMethods)) {
380354
return;
381355
}
382356
parameter.type = _computeParameterType(parameter, i, overriddenMethods);
357+
if (element is PropertyAccessorElement) {
358+
_updateSyntheticVariableType(element);
359+
}
383360
}
384361
}
385362
}
386363

364+
/**
365+
* If the given [element] is a non-synthetic getter or setter, update its
366+
* synthetic variable's type to match the getter's return type, or if no
367+
* corresponding getter exists, use the setter's parameter type.
368+
*
369+
* In general, the type of the synthetic variable should not be used, because
370+
* getters and setters are independent methods. But this logic matches what
371+
* `TypeResolverVisitor.visitMethodDeclaration` would fill in there.
372+
*/
373+
void _updateSyntheticVariableType(PropertyAccessorElement element) {
374+
assert(!element.isSynthetic);
375+
PropertyAccessorElement getter = element;
376+
if (element.isSetter) {
377+
// See if we can find any getter.
378+
getter = element.correspondingGetter;
379+
}
380+
DartType newType;
381+
if (getter != null) {
382+
newType = getter.returnType;
383+
} else if (element.isSetter && element.parameters.isNotEmpty) {
384+
newType = element.parameters[0].type;
385+
}
386+
if (newType != null) {
387+
(element.variable as VariableElementImpl).type = newType;
388+
}
389+
}
390+
387391
/**
388392
* Infer type information for all of the instance members in the given
389393
* interface [type].
@@ -426,13 +430,9 @@ class InstanceMemberInferrer {
426430
/**
427431
* Return `true` if the list of [elements] contains only methods.
428432
*/
429-
bool _onlyMethods(List<ExecutableElement> elements) {
430-
for (ExecutableElement element in elements) {
431-
if (element is! MethodElement) {
432-
return false;
433-
}
434-
}
435-
return true;
433+
bool _allSameElementKind(
434+
ExecutableElement element, List<ExecutableElement> elements) {
435+
return elements.every((e) => e.kind == element.kind);
436436
}
437437
}
438438

pkg/analyzer/test/src/task/strong_mode_test.dart

+88-1
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,9 @@ class B extends A {
458458
var get $getterName => 1;
459459
}
460460
''');
461+
ClassElement classA = unit.getType('A');
462+
FieldElement fieldA = classA.getField(getterName);
463+
PropertyAccessorElement getterA = classA.getGetter(getterName);
461464
ClassElement classB = unit.getType('B');
462465
FieldElement fieldB = classB.getField(getterName);
463466
PropertyAccessorElement getterB = classB.getGetter(getterName);
@@ -466,8 +469,92 @@ class B extends A {
466469

467470
inferrer.inferCompilationUnit(unit);
468471

472+
// Expected behavior is that the getter is inferred: getters and setters
473+
// are treated as independent methods.
474+
expect(fieldB.type, fieldA.type);
475+
expect(getterB.returnType, getterA.returnType);
476+
}
477+
478+
void test_inferCompilationUnit_setter_single() {
479+
InstanceMemberInferrer inferrer = createInferrer;
480+
String setterName = 'g';
481+
CompilationUnitElement unit = resolve('''
482+
class A {
483+
set $setterName(int x) {}
484+
}
485+
class B extends A {
486+
set $setterName(x) {}
487+
}
488+
''');
489+
ClassElement classA = unit.getType('A');
490+
FieldElement fieldA = classA.getField(setterName);
491+
PropertyAccessorElement setterA = classA.getSetter(setterName);
492+
ClassElement classB = unit.getType('B');
493+
FieldElement fieldB = classB.getField(setterName);
494+
PropertyAccessorElement setterB = classB.getSetter(setterName);
469495
expect(fieldB.type.isDynamic, isTrue);
470-
expect(getterB.returnType.isDynamic, isTrue);
496+
expect(setterB.parameters[0].type.isDynamic, isTrue);
497+
498+
inferrer.inferCompilationUnit(unit);
499+
500+
expect(fieldB.type, fieldA.type);
501+
expect(setterB.parameters[0].type, setterA.parameters[0].type);
502+
}
503+
504+
void test_inferCompilationUnit_setter_single_generic() {
505+
InstanceMemberInferrer inferrer = createInferrer;
506+
String setterName = 'g';
507+
CompilationUnitElement unit = resolve('''
508+
class A<E> {
509+
set $setterName(E x) {}
510+
}
511+
class B<E> extends A<E> {
512+
set $setterName(x) {}
513+
}
514+
''');
515+
ClassElement classB = unit.getType('B');
516+
DartType typeBE = classB.typeParameters[0].type;
517+
FieldElement fieldB = classB.getField(setterName);
518+
PropertyAccessorElement setterB = classB.getSetter(setterName);
519+
expect(fieldB.type.isDynamic, isTrue);
520+
expect(setterB.parameters[0].type.isDynamic, isTrue);
521+
522+
inferrer.inferCompilationUnit(unit);
523+
524+
expect(fieldB.type, typeBE);
525+
expect(setterB.parameters[0].type, typeBE);
526+
}
527+
528+
void test_inferCompilationUnit_setter_single_inconsistentAccessors() {
529+
InstanceMemberInferrer inferrer = createInferrer;
530+
String getterName = 'g';
531+
CompilationUnitElement unit = resolve('''
532+
class A {
533+
int get $getterName => 0;
534+
set $getterName(String value) {}
535+
}
536+
class B extends A {
537+
set $getterName(x) {}
538+
}
539+
''');
540+
ClassElement classA = unit.getType('A');
541+
FieldElement fieldA = classA.getField(getterName);
542+
PropertyAccessorElement setterA = classA.getSetter(getterName);
543+
ClassElement classB = unit.getType('B');
544+
FieldElement fieldB = classB.getField(getterName);
545+
PropertyAccessorElement setterB = classB.getSetter(getterName);
546+
expect(fieldB.type.isDynamic, isTrue);
547+
expect(setterB.parameters[0].type.isDynamic, isTrue);
548+
549+
inferrer.inferCompilationUnit(unit);
550+
551+
// Expected behavior is that the getter is inferred: getters and setters
552+
// are treated as independent methods.
553+
expect(setterB.parameters[0].type, setterA.parameters[0].type);
554+
555+
// Note that B's synthetic field type will be String. This matches what
556+
// resolver would do if we explicitly typed the parameter as 'String'
557+
expect(fieldB.type, setterB.parameters[0].type);
471558
}
472559

473560
void test_inferCompilationUnit_invalid_inheritanceCycle() {

0 commit comments

Comments
 (0)