Skip to content
This repository was archived by the owner on Jun 20, 2019. It is now read-only.

Solve issue #103 generic inherited types still unresolved #111

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 62 additions & 68 deletions analyzer_plugin/lib/src/tasks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,19 @@ class BuildUnitDirectivesTask extends SourceBasedAnalysisTask

TypeProvider typeProvider;

/**
* Since <my-comp></my-comp> represents an instantiation of MyComp,
* especially when MyComp is generic or its superclasses are, we need
* this. Cache instead of passing around everywhere.
*/
InterfaceType _instantiatedClassType;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use DartDoc style comments, /// or /** */.
EOL comments are not attached well enough to their target.


/**
* The [ClassElement] being used to create the current component,
* stored here instead of passing around everywhere.
*/
ClassElement _currentClassElement;

@override
void internalPerform() {
typeProvider = getRequiredInput(TYPE_PROVIDER_INPUT);
Expand Down Expand Up @@ -296,7 +309,8 @@ class BuildUnitDirectivesTask extends SourceBasedAnalysisTask
*/
AbstractDirective _createDirective(
ast.ClassDeclaration classDeclaration, ast.Annotation node) {
ClassElement classElement = classDeclaration.element;
_currentClassElement = classDeclaration.element;
_instantiatedClassType = _instantiateClass(_currentClassElement);
// TODO(scheglov) add support for all the arguments
bool isComponent = _isAngularAnnotation(node, 'Component');
bool isDirective = _isAngularAnnotation(node, 'Directive');
Expand All @@ -309,20 +323,20 @@ class BuildUnitDirectivesTask extends SourceBasedAnalysisTask
List<InputElement> inputElements = <InputElement>[];
List<OutputElement> outputElements = <OutputElement>[];
{
inputElements.addAll(_parseHeaderInputs(classElement, node));
outputElements.addAll(_parseHeaderOutputs(classElement, node));
inputElements.addAll(_parseHeaderInputs(node));
outputElements.addAll(_parseHeaderOutputs(node));
_parseMemberInputsAndOutputs(
classDeclaration, inputElements, outputElements);
}
if (isComponent) {
return new Component(classElement,
return new Component(_currentClassElement,
exportAs: exportAs,
inputs: inputElements,
outputs: outputElements,
selector: selector);
}
if (isDirective) {
return new Directive(classElement,
return new Directive(_currentClassElement,
exportAs: exportAs,
inputs: inputElements,
outputs: outputElements,
Expand Down Expand Up @@ -409,8 +423,7 @@ class BuildUnitDirectivesTask extends SourceBasedAnalysisTask
}
}

InputElement _parseHeaderInput(
ClassElement classElement, ast.Expression expression) {
InputElement _parseHeaderInput(ast.Expression expression) {
Tuple4<String, SourceRange, String, SourceRange> nameValueAndRanges =
_parseHeaderNameValueSourceRanges(expression);
if (nameValueAndRanges != null) {
Expand All @@ -419,25 +432,17 @@ class BuildUnitDirectivesTask extends SourceBasedAnalysisTask
var name = nameValueAndRanges.item3;
var nameRange = nameValueAndRanges.item4;

PropertyAccessorElement setter =
_resolveSetter(classElement, expression, name);

return new InputElement(
boundName,
boundRange.offset,
boundRange.length,
target.source,
setter,
nameRange,
_getSetterType(classElement, setter));
PropertyAccessorElement setter = _resolveSetter(expression, name);

return new InputElement(boundName, boundRange.offset, boundRange.length,
target.source, setter, nameRange, _getSetterType(setter));
} else {
// TODO(mfairhurst) report a warning
return null;
}
}

OutputElement _parseHeaderOutput(
ClassElement classElement, ast.Expression expression) {
OutputElement _parseHeaderOutput(ast.Expression expression) {
Tuple4<String, SourceRange, String, SourceRange> nameValueAndRanges =
_parseHeaderNameValueSourceRanges(expression);
if (nameValueAndRanges != null) {
Expand All @@ -446,10 +451,9 @@ class BuildUnitDirectivesTask extends SourceBasedAnalysisTask
var name = nameValueAndRanges.item3;
var nameRange = nameValueAndRanges.item4;

PropertyAccessorElement getter =
_resolveGetter(classElement, expression, name);
PropertyAccessorElement getter = _resolveGetter(expression, name);

var eventType = getEventType(classElement, getter, name);
var eventType = getEventType(getter, name);

return new OutputElement(boundName, boundRange.offset, boundRange.length,
target.source, getter, nameRange, eventType);
Expand All @@ -459,26 +463,34 @@ class BuildUnitDirectivesTask extends SourceBasedAnalysisTask
}
}

DartType _getSetterType(
ClassElement classElement, PropertyAccessorElement setter) {
DartType _getSetterType(PropertyAccessorElement setter) {
if (setter != null) {
setter = _instantiatedClassType.lookUpInheritedSetter(setter.name,
thisType: true);
}

if (setter != null && setter.variable != null) {
var type = setter.variable.type;
return _deparameterizeType(classElement, type);
return type;
}

return null;
}

DartType getEventType(
ClassElement classElement, PropertyAccessorElement getter, String name) {
DartType getEventType(PropertyAccessorElement getter, String name) {
if (getter != null) {
getter = _instantiatedClassType.lookUpInheritedGetter(getter.name,
thisType: true);
}

if (getter != null && getter.type != null) {
var returnType = getter.type.returnType;
if (returnType != null && returnType is InterfaceType) {
dart.DartType streamType = typeProvider.streamType;
dart.DartType streamedType =
context.typeSystem.mostSpecificTypeArgument(returnType, streamType);
if (streamedType != null) {
return _deparameterizeType(classElement, streamedType);
return streamedType;
} else {
errorReporter.reportErrorForNode(
AngularWarningCode.OUTPUT_MUST_BE_EVENTEMITTER,
Expand All @@ -496,27 +508,20 @@ class BuildUnitDirectivesTask extends SourceBasedAnalysisTask
return typeProvider.dynamicType;
}

DartType _deparameterizeType(
ClassElement classElement, DartType parameterizedType) {
if (parameterizedType == null) {
return null;
}

DartType _instantiateClass(ClassElement classElement) {
// TODO use `insantiateToBounds` for better all around support
// See #91 for discussion about bugs related to bounds
var getBound = (p) {
return p.bound == null
? typeProvider.dynamicType
: p.bound.resolveToBound(typeProvider.dynamicType);
};

var getType = (p) => p.type;
var bounds = classElement.typeParameters.map(getBound).toList();
var parameters = classElement.typeParameters.map(getType).toList();
return parameterizedType.substitute2(bounds, parameters);
return classElement.type.instantiate(bounds);
}

List<InputElement> _parseHeaderInputs(
ClassElement classElement, ast.Annotation node) {
List<InputElement> _parseHeaderInputs(ast.Annotation node) {
ast.ListLiteral descList = _getListLiteralNamedArgument(
node, const <String>['inputs', 'properties']);
if (descList == null) {
Expand All @@ -525,16 +530,15 @@ class BuildUnitDirectivesTask extends SourceBasedAnalysisTask
// Create an input for each element.
List<InputElement> inputElements = <InputElement>[];
for (ast.Expression element in descList.elements) {
InputElement inputElement = _parseHeaderInput(classElement, element);
InputElement inputElement = _parseHeaderInput(element);
if (inputElement != null) {
inputElements.add(inputElement);
}
}
return inputElements;
}

List<OutputElement> _parseHeaderOutputs(
ClassElement classElement, ast.Annotation node) {
List<OutputElement> _parseHeaderOutputs(ast.Annotation node) {
ast.ListLiteral descList =
_getListLiteralNamedArgument(node, const <String>['outputs']);
if (descList == null) {
Expand All @@ -543,7 +547,7 @@ class BuildUnitDirectivesTask extends SourceBasedAnalysisTask
// Create an output for each element.
List<OutputElement> outputs = <OutputElement>[];
for (ast.Expression element in descList.elements) {
OutputElement outputElement = _parseHeaderOutput(classElement, element);
OutputElement outputElement = _parseHeaderOutput(element);
if (outputElement != null) {
outputs.add(outputElement);
}
Expand All @@ -556,12 +560,8 @@ class BuildUnitDirectivesTask extends SourceBasedAnalysisTask
* the given `@Input` or `@Output` [annotation], and add it to the
* [inputElements] or [outputElements] array.
*/
_parseMemberInputOrOutput(
ClassElement classElement,
ast.ClassMember node,
ast.Annotation annotation,
List<InputElement> inputElements,
List<OutputElement> outputElements) {
_parseMemberInputOrOutput(ast.ClassMember node, ast.Annotation annotation,
List<InputElement> inputElements, List<OutputElement> outputElements) {
// analyze the annotation
final isInput = _isAngularAnnotation(annotation, 'Input');
final isOutput = _isAngularAnnotation(annotation, 'Output');
Expand Down Expand Up @@ -623,16 +623,10 @@ class BuildUnitDirectivesTask extends SourceBasedAnalysisTask
}

if (isInput) {
inputElements.add(new InputElement(
name,
nameOffset,
nameLength,
target.source,
property,
null,
_getSetterType(classElement, property)));
inputElements.add(new InputElement(name, nameOffset, nameLength,
target.source, property, null, _getSetterType(property)));
} else {
var eventType = getEventType(classElement, property, name);
var eventType = getEventType(property, name);
outputElements.add(new OutputElement(name, nameOffset, nameLength,
target.source, property, null, eventType));
}
Expand All @@ -647,7 +641,7 @@ class BuildUnitDirectivesTask extends SourceBasedAnalysisTask
for (ast.ClassMember member in node.members) {
for (ast.Annotation annotation in member.metadata) {
_parseMemberInputOrOutput(
node.element, member, annotation, inputElements, outputElements);
member, annotation, inputElements, outputElements);
}
}
}
Expand Down Expand Up @@ -683,31 +677,31 @@ class BuildUnitDirectivesTask extends SourceBasedAnalysisTask
}

/**
* Resolve the input setter with the given [name] in [classElement].
* Resolve the input setter with the given [name] in [_currentClassElement].
* If undefined, report a warning and return `null`.
*/
PropertyAccessorElement _resolveSetter(
ClassElement classElement, ast.SimpleStringLiteral literal, String name) {
ast.SimpleStringLiteral literal, String name) {
PropertyAccessorElement setter =
classElement.lookUpSetter(name, classElement.library);
_currentClassElement.lookUpSetter(name, _currentClassElement.library);
if (setter == null) {
errorReporter.reportErrorForNode(StaticTypeWarningCode.UNDEFINED_SETTER,
literal, [name, classElement.displayName]);
literal, [name, _currentClassElement.displayName]);
}
return setter;
}

/**
* Resolve the output getter with the given [name] in [classElement].
* Resolve the output getter with the given [name] in [_currentClassElement].
* If undefined, report a warning and return `null`.
*/
PropertyAccessorElement _resolveGetter(
ClassElement classElement, ast.SimpleStringLiteral literal, String name) {
ast.SimpleStringLiteral literal, String name) {
PropertyAccessorElement getter =
classElement.lookUpGetter(name, classElement.library);
_currentClassElement.lookUpGetter(name, _currentClassElement.library);
if (getter == null) {
errorReporter.reportErrorForNode(StaticTypeWarningCode.UNDEFINED_GETTER,
literal, [name, classElement.displayName]);
literal, [name, _currentClassElement.displayName]);
}
return getter;
}
Expand Down
54 changes: 54 additions & 0 deletions analyzer_plugin/test/resolver_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,60 @@ class GenericComponent<T> {
errorListener.assertNoErrors();
}

void test_expression_inputAndOutputBinding_genericDirectiveChild_ok() {
_addDartSource(r'''
@Component(selector: 'test-panel',
directives: const [GenericComponent], templateUrl: 'test_panel.html')
class TestPanel {
String string;
}
class Generic<T> {
EventEmitter<T> output;
T input;

EventEmitter<T> twoWayChange;
T twoWay;
}
@Component(selector: 'generic-comp', template: '',
inputs: ['input', 'twoWay'], outputs: ['output', 'twoWayChange'])
class GenericComponent<T> extends Generic<T> {
}
''');
var code = r"""
<generic-comp (output)='$event.length' [input]="string" [(twoWay)]="string"></generic-comp>
""";
_addHtmlSource(code);
_resolveSingleTemplate(dartSource);
errorListener.assertNoErrors();
}

void test_expression_inputAndOutputBinding_extendGenericUnbounded_ok() {
_addDartSource(r'''
@Component(selector: 'test-panel',
directives: const [GenericComponent], templateUrl: 'test_panel.html')
class TestPanel {
String string;
}
class Generic<T> {
EventEmitter<T> output;
T input;

EventEmitter<T> twoWayChange;
T twoWay;
}
@Component(selector: 'generic-comp', template: '',
inputs: ['input', 'twoWay'], outputs: ['output', 'twoWayChange'])
class GenericComponent<T> extends Generic {
}
''');
var code = r"""
<generic-comp (output)='$event.length' [input]="string" [(twoWay)]="string"></generic-comp>
""";
_addHtmlSource(code);
_resolveSingleTemplate(dartSource);
errorListener.assertNoErrors();
}

void test_expression_inputAndOutputBinding_genericDirective_chain_ok() {
_addDartSource(r'''
@Component(selector: 'test-panel',
Expand Down
Loading