blob: a8fe86895e414cfe1b2c03c6f75e8b0334c77ae6 [file] [log] [blame]
// Copyright (c) 2014, 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.
/// The models used to represent Dart code.
library dartdoc.element_type;
import 'dart:collection';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:dartdoc/src/model/model.dart';
import 'package:dartdoc/src/render/element_type_renderer.dart';
/// Base class representing a type in Dartdoc. It wraps a [DartType], and
/// may link to a [ModelElement].
abstract class ElementType extends Privacy {
final DartType _type;
final PackageGraph packageGraph;
final ElementType returnedFrom;
final Library library;
ElementType(this._type, this.library, this.packageGraph, this.returnedFrom);
factory ElementType.from(
DartType f, Library library, PackageGraph packageGraph,
[ElementType returnedFrom]) {
if (f.element == null || f.element.kind == ElementKind.DYNAMIC) {
if (f is FunctionType) {
return FunctionTypeElementType(f, library, packageGraph, returnedFrom);
}
return UndefinedElementType(f, library, packageGraph, returnedFrom);
} else {
ModelElement element = ModelElement.fromElement(f.element, packageGraph);
assert(f is ParameterizedType || f is TypeParameterType);
bool isGenericTypeAlias =
f.element.enclosingElement is GenericTypeAliasElement;
if (f is FunctionType) {
assert(f is ParameterizedType);
if (isGenericTypeAlias) {
assert(element is! ModelFunctionAnonymous);
return CallableGenericTypeAliasElementType(
f, library, packageGraph, element, returnedFrom);
} else {
if (element is ModelFunctionAnonymous) {
return CallableAnonymousElementType(
f, library, packageGraph, element, returnedFrom);
} else {
assert(element is! ModelFunctionAnonymous);
return CallableElementType(
f, library, packageGraph, element, returnedFrom);
}
}
} else if (isGenericTypeAlias) {
assert(f is TypeParameterType);
assert(element is! ModelFunctionAnonymous);
return GenericTypeAliasElementType(
f, library, packageGraph, element, returnedFrom);
}
if (f is TypeParameterType) {
assert(element is! ModelFunctionAnonymous);
return TypeParameterElementType(
f, library, packageGraph, element, returnedFrom);
}
assert(f is ParameterizedType);
assert(element is! ModelFunctionAnonymous);
return ParameterizedElementType(
f, library, packageGraph, element, returnedFrom);
}
}
bool get canHaveParameters => false;
// TODO(jcollins-g): change clients of ElementType to use subtypes more consistently
// and eliminate createLinkedReturnTypeName (instead, using returnType.linkedName);
String createLinkedReturnTypeName() => linkedName;
bool get isTypedef => false;
String get linkedName;
String get name => type.name ?? type.element.name;
String get nameWithGenerics;
List<Parameter> get parameters => [];
@override
String toString() => "$type";
DartType get type => _type;
}
/// An [ElementType] that isn't pinned to an Element (or one that is, but whose
/// element is irrelevant).
class UndefinedElementType extends ElementType {
UndefinedElementType(DartType f, Library library, PackageGraph packageGraph,
ElementType returnedFrom)
: super(f, library, packageGraph, returnedFrom);
String _linkedName;
@override
bool get isPublic => true;
@override
String get nameWithGenerics => name;
/// dynamic and void are not allowed to have parameterized types.
@override
String get linkedName {
if (type.isDynamic &&
returnedFrom != null &&
(returnedFrom is DefinedElementType &&
(returnedFrom as DefinedElementType).element.isAsynchronous)) {
return 'Future';
}
return name;
}
@override
String get name => type.name ?? '';
}
/// A FunctionType that does not have an underpinning Element.
class FunctionTypeElementType extends UndefinedElementType {
FunctionTypeElementType(DartType f, Library library,
PackageGraph packageGraph, ElementType returnedFrom)
: super(f, library, packageGraph, returnedFrom);
@override
List<Parameter> get parameters {
List<ParameterElement> params = (type as FunctionType).parameters;
return UnmodifiableListView<Parameter>(params
.map((p) => ModelElement.from(p, library, packageGraph) as Parameter)
.toList());
}
ElementType get returnType => ElementType.from(
(type as FunctionType).returnType, library, packageGraph, this);
@override
String get linkedName {
if (_linkedName == null) {
_linkedName =
FunctionTypeElementTypeRendererHtml().renderLinkedName(this);
}
return _linkedName;
}
@override
String createLinkedReturnTypeName() => returnType.linkedName;
String _nameWithGenerics;
@override
String get nameWithGenerics {
if (_nameWithGenerics == null) {
_nameWithGenerics =
FunctionTypeElementTypeRendererHtml().renderNameWithGenerics(this);
}
return _nameWithGenerics;
}
List<TypeParameter> get typeFormals {
List<TypeParameterElement> typeFormals = (type as FunctionType).typeFormals;
return UnmodifiableListView<TypeParameter>(typeFormals
.map(
(p) => ModelElement.from(p, library, packageGraph) as TypeParameter)
.toList());
}
@override
String get name => 'Function';
}
class ParameterizedElementType extends DefinedElementType {
ParameterizedElementType(ParameterizedType type, Library library,
PackageGraph packageGraph, ModelElement element, ElementType returnedFrom)
: super(type, library, packageGraph, element, returnedFrom);
String _linkedName;
@override
String get linkedName {
if (_linkedName == null) {
_linkedName =
ParameterizedElementTypeRendererHtml().renderLinkedName(this);
}
return _linkedName;
}
String _nameWithGenerics;
@override
String get nameWithGenerics {
if (_nameWithGenerics == null) {
_nameWithGenerics =
ParameterizedElementTypeRendererHtml().renderNameWithGenerics(this);
}
return _nameWithGenerics;
}
}
class TypeParameterElementType extends DefinedElementType {
TypeParameterElementType(TypeParameterType type, Library library,
PackageGraph packageGraph, ModelElement element, ElementType returnedFrom)
: super(type, library, packageGraph, element, returnedFrom);
@override
String get linkedName => name;
@override
String get nameWithGenerics => name;
@override
ClassElement get _boundClassElement => interfaceType.element;
@override
InterfaceType get interfaceType => (type as TypeParameterType).bound;
}
/// An [ElementType] associated with an [Element].
abstract class DefinedElementType extends ElementType {
final ModelElement _element;
DefinedElementType(DartType type, Library library, PackageGraph packageGraph,
this._element, ElementType returnedFrom)
: super(type, library, packageGraph, returnedFrom);
ModelElement get element {
assert(_element != null);
return _element;
}
bool get isParameterType => (type is TypeParameterType);
/// This type is a public type if the underlying, canonical element is public.
/// This avoids discarding the resolved type information as canonicalization
/// would ordinarily do.
@override
bool get isPublic {
Container canonicalClass =
element.packageGraph.findCanonicalModelElementFor(element.element) ??
element;
return canonicalClass.isPublic;
}
@override
bool get isTypedef => element is Typedef || element is ModelFunctionTypedef;
@override
List<Parameter> get parameters =>
element.canHaveParameters ? element.parameters : [];
ModelElement get returnElement => element;
ElementType _returnType;
ElementType get returnType {
if (_returnType == null) {
_returnType = ElementType.from(type, library, packageGraph, this);
}
return _returnType;
}
@override
String createLinkedReturnTypeName() => returnType.linkedName;
Iterable<ElementType> _typeArguments;
Iterable<ElementType> get typeArguments {
if (_typeArguments == null) {
_typeArguments = (type as ParameterizedType)
.typeArguments
.map((f) => ElementType.from(f, library, packageGraph))
.toList();
}
return _typeArguments;
}
/// By default, the bound is the type of the declared class.
ClassElement get _boundClassElement => (element.element as ClassElement);
Class get boundClass =>
ModelElement.fromElement(_boundClassElement, packageGraph);
InterfaceType get interfaceType => type;
InterfaceType _instantiatedType;
/// Return this type, instantiated to bounds if it isn't already.
DartType get instantiatedType {
if (_instantiatedType == null) {
if (!interfaceType.typeArguments.every((t) => t is InterfaceType)) {
_instantiatedType =
packageGraph.typeSystem.instantiateToBounds(interfaceType);
} else {
_instantiatedType = interfaceType;
}
}
return _instantiatedType;
}
}
/// Any callable ElementType will mix-in this class, whether anonymous or not.
abstract class CallableElementTypeMixin implements ParameterizedElementType {
@override
ModelElement get returnElement => returnType is DefinedElementType
? (returnType as DefinedElementType).element
: null;
@override
ElementType get returnType {
if (_returnType == null) {
_returnType =
ElementType.from(type.returnType, library, packageGraph, this);
}
return _returnType;
}
@override
FunctionType get type => _type;
@override
// TODO(jcollins-g): Rewrite this and improve object model so this doesn't
// require type checking everywhere.
Iterable<ElementType> get typeArguments {
if (_typeArguments == null) {
Iterable<DartType> dartTypeArguments;
if (returnedFrom is FunctionTypeElementType) {
if (type.typeFormals.isEmpty) {
dartTypeArguments = type.typeArguments;
} else {
dartTypeArguments = type.typeFormals.map(_legacyTypeParameterType);
}
} else {
DefinedElementType elementType = returnedFrom as DefinedElementType;
if (type.typeFormals.isEmpty &&
element is! ModelFunctionAnonymous &&
elementType?.element is! ModelFunctionAnonymous) {
dartTypeArguments = type.typeArguments;
} else if (returnedFrom != null &&
returnedFrom.type.element is GenericFunctionTypeElement) {
_typeArguments = (returnedFrom as DefinedElementType).typeArguments;
} else {
dartTypeArguments = type.typeFormals.map(_legacyTypeParameterType);
}
}
if (dartTypeArguments != null) {
_typeArguments = dartTypeArguments
.map((f) => ElementType.from(f, library, packageGraph))
.toList();
}
}
return _typeArguments;
}
/// Return the [TypeParameterType] with the legacy nullability for the given
/// type parameter [element].
///
/// TODO(scheglov) This method is a work around that fact that DartDoc
/// currently represents both type formals and uses of them as actual types,
/// as [TypeParameterType]s. This was not perfect, but worked before NNBD.
/// With NNBD types have nullability suffixes, but type formals should not.
/// Eventually we should separate models for type formals and types.
static TypeParameterType _legacyTypeParameterType(
TypeParameterElement element,
) {
return element.instantiate(nullabilitySuffix: NullabilitySuffix.star);
}
}
/// A callable type that may or may not be backed by a declaration using the generic
/// function syntax.
class CallableElementType extends ParameterizedElementType
with CallableElementTypeMixin {
CallableElementType(FunctionType t, Library library,
PackageGraph packageGraph, ModelElement element, ElementType returnedFrom)
: super(t, library, packageGraph, element, returnedFrom);
@override
String get linkedName {
if (_linkedName == null) {
_linkedName = CallableElementTypeRendererHtml().renderLinkedName(this);
}
return _linkedName;
}
String get superLinkedName => super.linkedName;
}
/// This is an anonymous function using the generic function syntax (declared
/// literally with "Function").
class CallableAnonymousElementType extends CallableElementType {
CallableAnonymousElementType(FunctionType t, Library library,
PackageGraph packageGraph, ModelElement element, ElementType returnedFrom)
: super(t, library, packageGraph, element, returnedFrom);
@override
String get name => 'Function';
@override
String get linkedName {
if (_linkedName == null) {
_linkedName =
CallableAnonymousElementTypeRendererHtml().renderLinkedName(this);
}
return _linkedName;
}
}
/// 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 {
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 {
if (_returnElement == null) {
_returnElement =
ModelElement.fromElement(type.element.enclosingElement, packageGraph);
}
return _returnElement;
}
@override
ElementType get returnType {
if (_returnType == null) {
_returnType =
ElementType.from(type.returnType, library, packageGraph, this);
}
return _returnType;
}
}