-
Notifications
You must be signed in to change notification settings - Fork 124
Do not depend on FunctionType being a subclass of ParameterizedType #2665
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4a0d9c6
31fa623
6e045ef
e95dbd4
97525d5
4a2f667
c9b40ee
04334b2
2a440a0
7b2d063
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,11 +26,15 @@ abstract class ElementType extends Privacy with CommentReferable, Nameable { | |
|
||
factory ElementType.from( | ||
DartType f, Library library, PackageGraph packageGraph, | ||
[ElementType returnedFrom]) { | ||
{ElementType returnedFrom}) { | ||
if (f.element == null || | ||
f.element.kind == ElementKind.DYNAMIC || | ||
f.element.kind == ElementKind.NEVER) { | ||
if (f is FunctionType) { | ||
if (f.aliasElement != null) { | ||
return AliasedFunctionTypeElementType( | ||
f, library, packageGraph, returnedFrom); | ||
} | ||
return FunctionTypeElementType(f, library, packageGraph, returnedFrom); | ||
} | ||
return UndefinedElementType(f, library, packageGraph, returnedFrom); | ||
|
@@ -50,10 +54,13 @@ abstract class ElementType extends Privacy with CommentReferable, Nameable { | |
var isGenericTypeAlias = f.aliasElement != null && f is! InterfaceType; | ||
if (f is FunctionType) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, this is probably what you mean about being backward compatible. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
You're also seeing me remove some leftovers from a previous "be compatible with old and new analyzer" iteration, it's now down to an assert rather than branching into the old There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, perhaps you meant, why is |
||
assert(f is ParameterizedType); | ||
if (isGenericTypeAlias) { | ||
return CallableGenericTypeAliasElementType( | ||
f, library, packageGraph, element, returnedFrom); | ||
} | ||
// This is an indication we have an extremely out of date analyzer.... | ||
assert( | ||
!isGenericTypeAlias, 'should never occur: out of date analyzer?'); | ||
// And finally, delete this case and its associated class | ||
// after https://dart-review.googlesource.com/c/sdk/+/201520 | ||
// is in all published versions of analyzer this version of dartdoc | ||
// is compatible with. | ||
return CallableElementType( | ||
f, library, packageGraph, element, returnedFrom); | ||
} else if (isGenericTypeAlias) { | ||
|
@@ -93,11 +100,10 @@ abstract class ElementType extends Privacy with CommentReferable, Nameable { | |
return ''; | ||
} | ||
|
||
/// An unmodifiable list of this element type's parameters. | ||
List<Parameter> get parameters; | ||
|
||
DartType get instantiatedType; | ||
|
||
Iterable<ElementType> get typeArguments; | ||
|
||
bool isBoundSupertypeTo(ElementType t); | ||
bool isSubtypeOf(ElementType t); | ||
|
||
|
@@ -117,33 +123,20 @@ class UndefinedElementType extends ElementType { | |
@override | ||
Element get element => null; | ||
|
||
String _linkedName; | ||
|
||
@override | ||
bool get isPublic => true; | ||
|
||
@override | ||
String get name { | ||
if (isImpliedFuture) return 'Future'; | ||
if (type.isVoid) return 'void'; | ||
assert({'Never', 'void', 'dynamic'}.contains(type.element.name), | ||
'Unrecognized type for UndefinedElementType: ${type.toString()}'); | ||
return type.element.name; | ||
} | ||
|
||
/// Returns true if this type is an implied `Future`. | ||
bool get isImpliedFuture => (type.isDynamic && | ||
returnedFrom != null && | ||
returnedFrom is DefinedElementType && | ||
(returnedFrom as DefinedElementType).modelElement.isAsynchronous); | ||
|
||
@override | ||
String get nameWithGenerics => '$name$nullabilitySuffix'; | ||
|
||
@override | ||
String get nullabilitySuffix => | ||
isImpliedFuture && library.isNullSafety ? '?' : super.nullabilitySuffix; | ||
|
||
/// Assume that undefined elements don't have useful bounds. | ||
@override | ||
DartType get instantiatedType => type; | ||
|
@@ -158,45 +151,22 @@ class UndefinedElementType extends ElementType { | |
String get linkedName => name; | ||
|
||
@override | ||
Map<String, CommentReferable> get referenceChildren => {}; | ||
Iterable<ElementType> get typeArguments => []; | ||
|
||
@override | ||
Iterable<CommentReferable> get referenceParents => []; | ||
Map<String, CommentReferable> get referenceChildren => {}; | ||
|
||
@override | ||
// TODO(jcollins-g): remove the need for an empty list here. | ||
List<Parameter> get parameters => []; | ||
Iterable<CommentReferable> get referenceParents => []; | ||
} | ||
|
||
/// A FunctionType that does not have an underpinning Element. | ||
class FunctionTypeElementType extends UndefinedElementType | ||
with CallableElementTypeMixin { | ||
FunctionTypeElementType(DartType f, Library library, | ||
with Rendered, Callable { | ||
FunctionTypeElementType(FunctionType f, Library library, | ||
PackageGraph packageGraph, ElementType returnedFrom) | ||
: super(f, library, packageGraph, returnedFrom); | ||
|
||
@override | ||
List<Parameter> get parameters => type.parameters | ||
.map((p) => ModelElement.from(p, library, packageGraph) as Parameter) | ||
.toList(growable: false); | ||
|
||
@override | ||
ElementType get returnType => | ||
ElementType.from(type.returnType, library, packageGraph, this); | ||
|
||
@override | ||
String get linkedName { | ||
_linkedName ??= _renderer.renderLinkedName(this); | ||
return _linkedName; | ||
} | ||
|
||
String _nameWithGenerics; | ||
@override | ||
String get nameWithGenerics { | ||
_nameWithGenerics ??= _renderer.renderNameWithGenerics(this); | ||
return _nameWithGenerics; | ||
} | ||
|
||
/// An unmodifiable list of this function element's type parameters. | ||
List<TypeParameter> get typeFormals => type.typeFormals | ||
.map((p) => ModelElement.from(p, library, packageGraph) as TypeParameter) | ||
|
@@ -205,39 +175,42 @@ class FunctionTypeElementType extends UndefinedElementType | |
@override | ||
String get name => 'Function'; | ||
|
||
ElementTypeRenderer<FunctionTypeElementType> get _renderer => | ||
@override | ||
ElementTypeRenderer get _renderer => | ||
packageGraph.rendererFactory.functionTypeElementTypeRenderer; | ||
} | ||
|
||
class ParameterizedElementType extends DefinedElementType { | ||
class AliasedFunctionTypeElementType extends FunctionTypeElementType | ||
with Aliased { | ||
AliasedFunctionTypeElementType(FunctionType f, Library library, | ||
PackageGraph packageGraph, ElementType returnedFrom) | ||
: super(f, library, packageGraph, returnedFrom) { | ||
assert(type.aliasElement != null); | ||
assert(type.aliasArguments != null); | ||
} | ||
|
||
@override | ||
ElementTypeRenderer<AliasedFunctionTypeElementType> get _renderer => | ||
packageGraph.rendererFactory.aliasedFunctionTypeElementTypeRenderer; | ||
} | ||
|
||
class ParameterizedElementType extends DefinedElementType with Rendered { | ||
ParameterizedElementType(ParameterizedType type, Library library, | ||
PackageGraph packageGraph, ModelElement element, ElementType returnedFrom) | ||
: super(type, library, packageGraph, element, returnedFrom); | ||
|
||
String _linkedName; | ||
@override | ||
String get linkedName { | ||
_linkedName ??= _renderer.renderLinkedName(this); | ||
return _linkedName; | ||
} | ||
|
||
String _nameWithGenerics; | ||
@override | ||
String get nameWithGenerics { | ||
_nameWithGenerics ??= _renderer.renderNameWithGenerics(this); | ||
return _nameWithGenerics; | ||
} | ||
|
||
ElementTypeRenderer<ParameterizedElementType> get _renderer => | ||
packageGraph.rendererFactory.parameterizedElementTypeRenderer; | ||
} | ||
|
||
class AliasedElementType extends ParameterizedElementType { | ||
AliasedElementType(ParameterizedType type, Library library, | ||
PackageGraph packageGraph, ModelElement element, ElementType returnedFrom) | ||
: super(type, library, packageGraph, element, returnedFrom) { | ||
assert(type.aliasElement != null); | ||
} | ||
/// A [ElementType] whose underlying type was referrred to by a type alias. | ||
mixin Aliased implements ElementType { | ||
@override | ||
String get name => type.aliasElement.name; | ||
|
||
@override | ||
bool get isTypedef => true; | ||
|
||
ModelElement _aliasElement; | ||
ModelElement get aliasElement => _aliasElement ??= | ||
|
@@ -249,6 +222,26 @@ class AliasedElementType extends ParameterizedElementType { | |
.map((f) => ElementType.from(f, library, packageGraph)) | ||
.toList(growable: false); | ||
|
||
Iterable<ElementType> _typeArguments; | ||
@override | ||
Iterable<ElementType> get typeArguments => | ||
_typeArguments ??= (type as ParameterizedType) | ||
.typeArguments | ||
.map((f) => ElementType.from(f, library, packageGraph)) | ||
.toList(growable: false); | ||
} | ||
|
||
class AliasedElementType extends ParameterizedElementType with Aliased { | ||
AliasedElementType(ParameterizedType type, Library library, | ||
PackageGraph packageGraph, ModelElement element, ElementType returnedFrom) | ||
: super(type, library, packageGraph, element, returnedFrom) { | ||
assert(type.aliasElement != null); | ||
} | ||
|
||
/// Parameters, if available, for the underlying typedef. | ||
List<Parameter> get aliasedParameters => | ||
modelElement.isCallable ? modelElement.parameters : []; | ||
|
||
@override | ||
ElementTypeRenderer<AliasedElementType> get _renderer => | ||
packageGraph.rendererFactory.aliasedElementTypeRenderer; | ||
|
@@ -304,24 +297,8 @@ abstract class DefinedElementType extends ElementType { | |
return canonicalClass?.isPublic ?? false; | ||
} | ||
|
||
@override | ||
bool get isTypedef => | ||
modelElement is Typedef || modelElement is ModelFunctionTypedef; | ||
|
||
@override | ||
List<Parameter> get parameters => | ||
modelElement.isCallable ? modelElement.parameters : []; | ||
|
||
ModelElement get returnElement => modelElement; | ||
ElementType _returnType; | ||
ElementType get returnType { | ||
_returnType ??= ElementType.from(type, library, packageGraph, this); | ||
return _returnType; | ||
} | ||
|
||
Iterable<ElementType> _typeArguments; | ||
|
||
/// An unmodifiable list of this element type's parameters. | ||
@override | ||
Iterable<ElementType> get typeArguments => | ||
_typeArguments ??= (type as ParameterizedType) | ||
.typeArguments | ||
|
@@ -383,38 +360,46 @@ abstract class DefinedElementType extends ElementType { | |
modelElement.referenceParents; | ||
} | ||
|
||
/// Any callable ElementType will mix-in this class, whether anonymous or not. | ||
mixin CallableElementTypeMixin implements ElementType { | ||
@override | ||
// TODO(jcollins-g): remove after dart-lang/dartdoc#2648 is fixed. | ||
String get linkedName; | ||
|
||
ModelElement get returnElement => returnType is DefinedElementType | ||
? (returnType as DefinedElementType).modelElement | ||
: null; | ||
/// Any callable ElementType will mix-in this class, whether anonymous or not, | ||
/// unless it is an alias reference. | ||
mixin Callable implements ElementType { | ||
List<Parameter> get parameters => type.parameters | ||
.map((p) => ModelElement.from(p, library, packageGraph) as Parameter) | ||
.toList(growable: false); | ||
|
||
ElementType _returnType; | ||
ElementType get returnType { | ||
_returnType ??= | ||
ElementType.from(type.returnType, library, packageGraph, this); | ||
_returnType ??= ElementType.from(type.returnType, library, packageGraph); | ||
return _returnType; | ||
} | ||
|
||
@override | ||
FunctionType get type => _type; | ||
} | ||
|
||
Iterable<ElementType> _typeArguments; | ||
Iterable<ElementType> get typeArguments => | ||
_typeArguments ??= type.aliasArguments | ||
?.map((f) => ElementType.from(f, library, packageGraph)) | ||
?.toList() ?? | ||
[]; | ||
/// This [ElementType] uses an [ElementTypeRenderer] to generate | ||
/// some of its parameters. | ||
mixin Rendered implements ElementType { | ||
String _linkedName; | ||
@override | ||
String get linkedName { | ||
_linkedName ??= _renderer.renderLinkedName(this); | ||
return _linkedName; | ||
} | ||
|
||
String _nameWithGenerics; | ||
@override | ||
String get nameWithGenerics { | ||
_nameWithGenerics ??= _renderer.renderNameWithGenerics(this); | ||
return _nameWithGenerics; | ||
} | ||
|
||
ElementTypeRenderer<ElementType> get _renderer; | ||
} | ||
|
||
/// A callable type that may or may not be backed by a declaration using the generic | ||
/// function syntax. | ||
class CallableElementType extends ParameterizedElementType | ||
with CallableElementTypeMixin { | ||
class CallableElementType extends DefinedElementType with Rendered, Callable { | ||
CallableElementType(FunctionType t, Library library, | ||
PackageGraph packageGraph, ModelElement element, ElementType returnedFrom) | ||
: super(t, library, packageGraph, element, returnedFrom); | ||
|
@@ -428,39 +413,9 @@ class CallableElementType extends ParameterizedElementType | |
packageGraph.rendererFactory.callableElementTypeRenderer; | ||
} | ||
|
||
/// Types backed by a [GenericTypeAliasElement] that may or may not be callable. | ||
abstract class GenericTypeAliasElementTypeMixin {} | ||
|
||
/// A non-callable type backed by a [GenericTypeAliasElement]. | ||
class GenericTypeAliasElementType extends TypeParameterElementType | ||
with GenericTypeAliasElementTypeMixin { | ||
class GenericTypeAliasElementType extends TypeParameterElementType { | ||
GenericTypeAliasElementType(TypeParameterType t, Library library, | ||
PackageGraph packageGraph, ModelElement element, ElementType returnedFrom) | ||
: super(t, library, packageGraph, element, returnedFrom); | ||
} | ||
|
||
/// A Callable generic type alias that may or may not have a name. | ||
class CallableGenericTypeAliasElementType extends ParameterizedElementType | ||
with CallableElementTypeMixin, GenericTypeAliasElementTypeMixin { | ||
CallableGenericTypeAliasElementType(FunctionType t, Library library, | ||
PackageGraph packageGraph, ModelElement element, ElementType returnedFrom) | ||
: super(t, library, packageGraph, element, returnedFrom); | ||
|
||
ModelElement _returnElement; | ||
@override | ||
ModelElement get returnElement { | ||
_returnElement ??= ModelElement.fromElement( | ||
type.aliasElement.enclosingElement, packageGraph); | ||
return _returnElement; | ||
} | ||
|
||
@override | ||
ElementType get returnType { | ||
_returnType ??= | ||
ElementType.from(type.returnType, library, packageGraph, this); | ||
return _returnType; | ||
} | ||
|
||
@override | ||
DartType get instantiatedType => type; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Null safety suggestion: make
aliasElement
andaliasArguments
required parameters ofAliasedFunctionTypeElementType
. It is nice to use the type system to prove that some data is available. Here, we have already proved thataliasElement
is available. But then we drop this proof, and forceAliasedFunctionTypeElementType
to implicitly require thataliasElement
andaliasArguments
are notnull
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The TL;DR is no, not doing that, because dartdoc isn't written this way in general and to introduce a contradictory paradigm would be confusing unless it propagated throughout the entire codebase, which is a lot of work. Making the requirement more explicit within the constructor instead.
However, this probably deserves more explanation (at least I would want more if I were you) so here we go. This includes a lot of background, you may know some already, but for posterity...
First, the primary purpose of this if statement is not to prove that
aliasElement
is available, but rather to select the appropriate constructor if it is available. Dartdoc models "how to structure and render code in documentation" within the class structure and type system, vs the analyzer's "how to represent the code to answer questions around static analysis". An ancient choice in dartdoc on how to do this is as an overlay to the analyzer, essentially creating a custom view of the element model. This is done with one-to-one, many-to-one, or one-to-many dartdoc classes for a given analyzer class. This makes some sense, since how you view the code in documentation is closely related to its structure as written, which is closely related to how it is represented in the analyzer.I am not sure if the original author of dartdoc was conscious of this design choice and all its implications, and I am pretty sure they didn't think of NNBD when making this choice! But it is kind of baked in to the entire piece of software and has been extended by myself and others since then with varying levels of consistency. What is happening in this PR is not that we're proving that
aliasElement
is present, we're using its presence to decide which object to construct (AliasedFunctionTypeElementType
orFunctionTypeElementType
), and therefore, eventually, how we will render theFunctionType
. It just sort of accidentally proves that it is available along the way. Essentially, the code I wrote just wishes those getters didn't exist in analyzer types where we don't care about them, but because they do, we do this little dance.This is consistent with dartdoc's design choice of basing every object off a corresponding analyzer object. Leading me to the second part, around making dartdoc internally consistent. We can argue about whether the design choice is a good idea -- if I was building this thing from scratch I might make different choices, but, given where we are... Were I to pass
aliasElement
andaliasArguments
through to the constructor here, we'd be doing something that isn't typically done in the rest of dartdoc. We try not to pass things that can be acquired directly from the object we are representing, though sometimes for convenience we do pass the Dartdoc equivalent of those objects to constructors.In addition to the design inconsistency, a second consequence is now you have the wrong way to access aliasElement (
type.aliasElement
) and the right way (analyzerAliasElement
?, since we already havealiasElement
that is a Dartdoc object) with no way to enforce which one you are using.While maybe we are smart enough to always use the right way of accessing members of
DartType
I do not trust myself a few months from now to reread this code and use the right one all the time. In most cases getters in dartdoc likealiasElement
are converted Dartdoc objects rather than the analyzer objects, and you can see me doing that in this PR. In addition, not all used getters oftype
are represented, should we also passelement
?element.name
? (Remember, evenname
is the name as Dartdoc sees it, which is occasionally different from the analyzer's view in Dartdoc objects.element
is actually slated for demolition inElementType
and I don't want to keep it around -- again, trying to reduce inconsistency here rather than increase it).We could also fix that "multiple paths" problem by eliminating the DartType entirely from ElementType constructors and always passing everything we use in or otherwise discarding the original object. This however is an even more jarring design choice given the rest of Dartdoc. At this point I'd rather just redesign/reimplement the whole program because that will be enormous and maybe not worth the effort. I certainly wouldn't want to introduce this without converting most of dartdoc to use this style. And that would involve a lot of boilerplate code plus make debugging more difficult (i.e. where did this name/element/aliasElement come from in the analyzer, which is a question I ask quite often in diagnosing problems).
So given these issues, I will resist passing more parameters that can be acquired via getters in the represented analyzer object unless there's a case compelling enough to overcome the long term maintenance problems that can create. Or, if there's a lot of time to restructure dartdoc around this. Maybe a better path will become more clear after Dartdoc is converted to NNBD, which if it isn't going to be done this quarter, will be a P1 next quarter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow, this is a very thorough explanation, thank you for it.
I appreciate the intention to keep code consistent, and was merely sharing my view on writing more null safe code. But DartDoc is not null safe, so it might be premature to do anyway :-)