Skip to content

Generalize TypeBoundsTree to bounded opaque aliases #8212

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

Merged
merged 3 commits into from
Feb 6, 2020
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
7 changes: 4 additions & 3 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ object desugar {

if (meth1.mods.is(Inline))
meth1.tpt match {
case TypeBoundsTree(_, tpt1) =>
case TypeBoundsTree(_, tpt1, _) =>
meth1 = cpy.DefDef(meth1)(tpt = tpt1)
case tpt if !tpt.isEmpty && !meth1.rhs.isEmpty =>
meth1 = cpy.DefDef(meth1)(rhs = Typed(meth1.rhs, tpt))
Expand Down Expand Up @@ -684,7 +684,7 @@ object desugar {
val mods = constr1.mods
mods.is(Private) || (!mods.is(Protected) && mods.hasPrivateWithin)
}

/** Does one of the parameter's types (in the first param clause)
* mention a preceding parameter?
*/
Expand Down Expand Up @@ -1156,7 +1156,8 @@ object desugar {
val legalOpaque: MemberDefTest = {
case TypeDef(_, rhs) =>
def rhsOK(tree: Tree): Boolean = tree match {
case _: TypeBoundsTree | _: Template => false
case bounds: TypeBoundsTree => !bounds.alias.isEmpty
case _: Template => false
case LambdaTypeTree(_, body) => rhsOK(body)
case _ => true
}
Expand Down
25 changes: 13 additions & 12 deletions compiler/src/dotty/tools/dotc/ast/Trees.scala
Original file line number Diff line number Diff line change
Expand Up @@ -690,8 +690,10 @@ object Trees {
type ThisTree[-T >: Untyped] = ByNameTypeTree[T]
}

/** >: lo <: hi */
case class TypeBoundsTree[-T >: Untyped] private[ast] (lo: Tree[T], hi: Tree[T])(implicit @constructorOnly src: SourceFile)
/** >: lo <: hi
* >: lo <: hi = alias for RHS of bounded opaque type
*/
case class TypeBoundsTree[-T >: Untyped] private[ast] (lo: Tree[T], hi: Tree[T], alias: Tree[T])(implicit @constructorOnly src: SourceFile)
extends TypTree[T] {
type ThisTree[-T >: Untyped] = TypeBoundsTree[T]
}
Expand Down Expand Up @@ -760,7 +762,8 @@ object Trees {
/** mods class name template or
* mods trait name template or
* mods type name = rhs or
* mods type name >: lo <: hi, if rhs = TypeBoundsTree(lo, hi) & (lo ne hi)
* mods type name >: lo <: hi, if rhs = TypeBoundsTree(lo, hi) or
* mods type name >: lo <: hi = rhs if rhs = TypeBoundsTree(lo, hi, alias) and opaque in mods
*/
case class TypeDef[-T >: Untyped] private[ast] (name: TypeName, rhs: Tree[T])(implicit @constructorOnly src: SourceFile)
extends MemberDef[T] {
Expand Down Expand Up @@ -811,8 +814,6 @@ object Trees {
extends ProxyTree[T] {
type ThisTree[-T >: Untyped] = Annotated[T]
def forwardTo: Tree[T] = arg
override def disableOverlapChecks = true
// disable overlaps checks since the WithBounds annotation swaps type and annotation.
}

trait WithoutTypeOrPos[-T >: Untyped] extends Tree[T] {
Expand Down Expand Up @@ -1151,9 +1152,9 @@ object Trees {
case tree: ByNameTypeTree if (result eq tree.result) => tree
case _ => finalize(tree, untpd.ByNameTypeTree(result)(sourceFile(tree)))
}
def TypeBoundsTree(tree: Tree)(lo: Tree, hi: Tree)(implicit ctx: Context): TypeBoundsTree = tree match {
case tree: TypeBoundsTree if (lo eq tree.lo) && (hi eq tree.hi) => tree
case _ => finalize(tree, untpd.TypeBoundsTree(lo, hi)(sourceFile(tree)))
def TypeBoundsTree(tree: Tree)(lo: Tree, hi: Tree, alias: Tree)(implicit ctx: Context): TypeBoundsTree = tree match {
case tree: TypeBoundsTree if (lo eq tree.lo) && (hi eq tree.hi) && (alias eq tree.alias) => tree
case _ => finalize(tree, untpd.TypeBoundsTree(lo, hi, alias)(sourceFile(tree)))
}
def Bind(tree: Tree)(name: Name, body: Tree)(implicit ctx: Context): Bind = tree match {
case tree: Bind if (name eq tree.name) && (body eq tree.body) => tree
Expand Down Expand Up @@ -1304,8 +1305,8 @@ object Trees {
cpy.MatchTypeTree(tree)(transform(bound), transform(selector), transformSub(cases))
case ByNameTypeTree(result) =>
cpy.ByNameTypeTree(tree)(transform(result))
case TypeBoundsTree(lo, hi) =>
cpy.TypeBoundsTree(tree)(transform(lo), transform(hi))
case TypeBoundsTree(lo, hi, alias) =>
cpy.TypeBoundsTree(tree)(transform(lo), transform(hi), transform(alias))
case Bind(name, body) =>
cpy.Bind(tree)(name, transform(body))
case Alternative(trees) =>
Expand Down Expand Up @@ -1428,8 +1429,8 @@ object Trees {
this(this(this(x, bound), selector), cases)
case ByNameTypeTree(result) =>
this(x, result)
case TypeBoundsTree(lo, hi) =>
this(this(x, lo), hi)
case TypeBoundsTree(lo, hi, alias) =>
this(this(this(x, lo), hi), alias)
case Bind(name, body) =>
this(x, body)
case Alternative(trees) =>
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/ast/tpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,8 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
def MatchTypeTree(bound: Tree, selector: Tree, cases: List[CaseDef])(implicit ctx: Context): MatchTypeTree =
ta.assignType(untpd.MatchTypeTree(bound, selector, cases), bound, selector, cases)

def TypeBoundsTree(lo: Tree, hi: Tree)(implicit ctx: Context): TypeBoundsTree =
ta.assignType(untpd.TypeBoundsTree(lo, hi), lo, hi)
def TypeBoundsTree(lo: Tree, hi: Tree, alias: Tree = EmptyTree)(implicit ctx: Context): TypeBoundsTree =
ta.assignType(untpd.TypeBoundsTree(lo, hi, alias), lo, hi, alias)

def Bind(sym: Symbol, body: Tree)(implicit ctx: Context): Bind =
ta.assignType(untpd.Bind(sym.name, body), sym)
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/ast/untpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
def LambdaTypeTree(tparams: List[TypeDef], body: Tree)(implicit src: SourceFile): LambdaTypeTree = new LambdaTypeTree(tparams, body)
def MatchTypeTree(bound: Tree, selector: Tree, cases: List[CaseDef])(implicit src: SourceFile): MatchTypeTree = new MatchTypeTree(bound, selector, cases)
def ByNameTypeTree(result: Tree)(implicit src: SourceFile): ByNameTypeTree = new ByNameTypeTree(result)
def TypeBoundsTree(lo: Tree, hi: Tree)(implicit src: SourceFile): TypeBoundsTree = new TypeBoundsTree(lo, hi)
def TypeBoundsTree(lo: Tree, hi: Tree, alias: Tree = EmptyTree)(implicit src: SourceFile): TypeBoundsTree = new TypeBoundsTree(lo, hi, alias)
def Bind(name: Name, body: Tree)(implicit src: SourceFile): Bind = new Bind(name, body)
def Alternative(trees: List[Tree])(implicit src: SourceFile): Alternative = new Alternative(trees)
def UnApply(fun: Tree, implicits: List[Tree], patterns: List[Tree])(implicit src: SourceFile): UnApply = new UnApply(fun, implicits, patterns)
Expand Down
16 changes: 0 additions & 16 deletions compiler/src/dotty/tools/dotc/core/Annotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -173,22 +173,6 @@ object Annotations {
else None
}

/** Extractor for WithBounds[T] annotations */
object WithBounds {
def unapply(ann: Annotation)(implicit ctx: Context): Option[TypeBounds] =
if (ann.symbol == defn.WithBoundsAnnot) {
import ast.Trees._
// We need to extract the type of the type tree in the New itself.
// The annotation's type has been simplified as the type of an expression,
// which means that `&` or `|` might have been lost.
// Test in pos/reference/opaque.scala
val Apply(TypeApply(Select(New(tpt), nme.CONSTRUCTOR), _), Nil) = ann.tree
val AppliedType(_, lo :: hi :: Nil) = tpt.tpe
Some(TypeBounds(lo, hi))
}
else None
}

def makeSourceFile(path: String)(implicit ctx: Context): Annotation =
apply(defn.SourceFileAnnot, Literal(Constant(path)))
}
Expand Down
1 change: 0 additions & 1 deletion compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -784,7 +784,6 @@ class Definitions {
@tu lazy val AnnotationDefaultAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.internal.AnnotationDefault")
@tu lazy val BodyAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.internal.Body")
@tu lazy val ChildAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.internal.Child")
@tu lazy val WithBoundsAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.internal.WithBounds")
@tu lazy val CovariantBetweenAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.internal.CovariantBetween")
@tu lazy val ContravariantBetweenAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.internal.ContravariantBetween")
@tu lazy val DeprecatedAnnot: ClassSymbol = ctx.requiredClass("scala.deprecated")
Expand Down
24 changes: 12 additions & 12 deletions compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Scopes.Scope
import dotty.tools.io.AbstractFile
import Decorators.SymbolIteratorDecorator
import ast._
import ast.Trees.{LambdaTypeTree, TypeBoundsTree}
import Trees.Literal
import Variances.Variance
import annotation.tailrec
Expand Down Expand Up @@ -433,8 +434,9 @@ object SymDenotations {
*
* @param info Is assumed to be a (lambda-abstracted) right hand side TypeAlias
* of the opaque type definition.
* @param rhs The right hand side tree of the type definition
*/
def opaqueToBounds(info: Type)(given Context): Type =
def opaqueToBounds(info: Type, rhs: tpd.Tree)(given Context): Type =

def setAlias(tp: Type) =
def recur(self: Type): Unit = self match
Expand All @@ -446,21 +448,19 @@ object SymDenotations {
recur(owner.asClass.givenSelfType)
end setAlias

def split(tp: Type): (Type, TypeBounds) = tp match
case AnnotatedType(alias, Annotation.WithBounds(bounds)) =>
(alias, bounds)
case tp: HKTypeLambda =>
val (alias1, bounds1) = split(tp.resType)
(tp.derivedLambdaType(resType = alias1),
HKTypeLambda.boundsFromParams(tp.typeParams, bounds1))
def bounds(t: tpd.Tree): TypeBounds = t match
case LambdaTypeTree(_, body) =>
bounds(body)
case TypeBoundsTree(lo, hi, alias) =>
assert(!alias.isEmpty)
TypeBounds(lo.tpe, hi.tpe)
case _ =>
(tp, HKTypeLambda.boundsFromParams(tp.typeParams, TypeBounds.empty))
TypeBounds.empty

info match
case TypeAlias(tp) if isOpaqueAlias && owner.isClass =>
val (alias, bounds) = split(tp)
case TypeAlias(alias) if isOpaqueAlias && owner.isClass =>
setAlias(alias)
bounds
HKTypeLambda.boundsFromParams(alias.typeParams, bounds(rhs))
case _ =>
info
end opaqueToBounds
Expand Down
8 changes: 6 additions & 2 deletions compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -588,11 +588,15 @@ class TreePickler(pickler: TastyPickler) {
case LambdaTypeTree(tparams, body) =>
writeByte(LAMBDAtpt)
withLength { pickleParams(tparams); pickleTree(body) }
case TypeBoundsTree(lo, hi) =>
case TypeBoundsTree(lo, hi, alias) =>
writeByte(TYPEBOUNDStpt)
withLength {
pickleTree(lo);
if (hi ne lo) pickleTree(hi)
if alias.isEmpty then
if hi ne lo then pickleTree(hi)
else
pickleTree(hi)
pickleTree(alias)
}
case Hole(_, idx, args) =>
writeByte(HOLE)
Expand Down
12 changes: 7 additions & 5 deletions compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -833,11 +833,12 @@ class TreeUnpickler(reader: TastyReader,
override def completerTypeParams(sym: Symbol)(implicit ctx: Context) =
rhs.tpe.typeParams
}
sym.info = sym.opaqueToBounds {
rhs.tpe match
sym.info = sym.opaqueToBounds(
rhs.tpe match {
case _: TypeBounds | _: ClassInfo => checkNonCyclic(sym, rhs.tpe, reportErrors = false)
case _ => rhs.tpe.toBounds
}
},
rhs)
if sym.isOpaqueAlias then sym.typeRef.recomputeDenot() // make sure we see the new bounds from now on
sym.resetFlag(Provisional)
TypeDef(rhs)
Expand Down Expand Up @@ -1209,8 +1210,9 @@ class TreeUnpickler(reader: TastyReader,
MatchTypeTree(bound, scrut, readCases(end))
case TYPEBOUNDStpt =>
val lo = readTpt()
val hi = if (currentAddr == end) lo else readTpt()
TypeBoundsTree(lo, hi)
val hi = if currentAddr == end then lo else readTpt()
val alias = if currentAddr == end then EmptyTree else readTpt()
TypeBoundsTree(lo, hi, alias)
case HOLE =>
readHole(end, isType = false)
case _ =>
Expand Down
16 changes: 6 additions & 10 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3344,20 +3344,16 @@ object Parsers {
rhs match {
case mtt: MatchTypeTree =>
bounds match {
case TypeBoundsTree(EmptyTree, upper) =>
case TypeBoundsTree(EmptyTree, upper, _) =>
rhs = MatchTypeTree(upper, mtt.selector, mtt.cases)
case _ =>
syntaxError(i"cannot combine lower bound and match type alias", eqOffset)
}
case _ =>
if (mods.is(Opaque)) {
val annotType = AppliedTypeTree(
TypeTree(defn.WithBoundsAnnot.typeRef),
bounds.lo.orElse(TypeTree(defn.NothingType)) ::
bounds.hi.orElse(TypeTree(defn.AnyType)) :: Nil)
rhs = Annotated(rhs, ensureApplied(wrapNew(annotType)))
}
else syntaxError(i"cannot combine bound and alias", eqOffset)
if mods.is(Opaque) then
rhs = TypeBoundsTree(bounds.lo, bounds.hi, rhs)
else
syntaxError(i"cannot combine bound and alias", eqOffset)
}
makeTypeDef(rhs)
}
Expand Down Expand Up @@ -3568,7 +3564,7 @@ object Parsers {
DefDef(name, tparams, vparamss, parents.head, subExpr())
else
parents match
case TypeBoundsTree(_, _) :: _ => syntaxError("`=` expected")
case (_: TypeBoundsTree) :: _ => syntaxError("`=` expected")
case _ =>
possibleTemplateStart()
val tparams1 = tparams.map(tparam => tparam.withMods(tparam.mods | PrivateLocal))
Expand Down
6 changes: 3 additions & 3 deletions compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -492,9 +492,9 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
}
case ByNameTypeTree(tpt) =>
"=> " ~ toTextLocal(tpt)
case TypeBoundsTree(lo, hi) =>
if (lo eq hi) optText(lo)(" = " ~ _)
else optText(lo)(" >: " ~ _) ~ optText(hi)(" <: " ~ _)
case TypeBoundsTree(lo, hi, alias) =>
if (lo eq hi) && alias.isEmpty then optText(lo)(" = " ~ _)
else optText(lo)(" >: " ~ _) ~ optText(hi)(" <: " ~ _) ~ optText(alias)(" = " ~ _)
case Bind(name, body) =>
keywordText("given ").provided(tree.symbol.isOneOf(GivenOrImplicit) && !homogenizedView) ~ // Used for scala.quoted.Type in quote patterns (not pickled)
changePrec(InfixPrec) { toText(name) ~ " @ " ~ toText(body) }
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ class ReifyQuotes extends MacroTransform {
def mkTagSymbolAndAssignType(spliced: TermRef): TypeDef = {
val splicedTree = tpd.ref(spliced).withSpan(expr.span)
val rhs = transform(splicedTree.select(tpnme.splice))
val alias = ctx.typeAssigner.assignType(untpd.TypeBoundsTree(rhs, rhs), rhs, rhs)
val alias = ctx.typeAssigner.assignType(untpd.TypeBoundsTree(rhs, rhs), rhs, rhs, EmptyTree)
val local = ctx.newSymbol(
owner = ctx.owner,
name = UniqueName.fresh((splicedTree.symbol.name.toString + "$_").toTermName).toTypeName,
Expand Down
5 changes: 3 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/Namer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -997,12 +997,13 @@ class Namer { typer: Typer =>
}
sym.info = dummyInfo2

val rhsBodyType: TypeBounds = typedAheadType(rhs).tpe.toBounds
val rhs1 = typedAheadType(rhs)
val rhsBodyType: TypeBounds = rhs1.tpe.toBounds
val unsafeInfo = if (isDerived) rhsBodyType else abstracted(rhsBodyType)
if (isDerived) sym.info = unsafeInfo
else {
sym.info = NoCompleter
sym.info = sym.opaqueToBounds(checkNonCyclic(sym, unsafeInfo, reportErrors = true))
sym.info = sym.opaqueToBounds(checkNonCyclic(sym, unsafeInfo, reportErrors = true), rhs1)
}
if sym.isOpaqueAlias then sym.typeRef.recomputeDenot() // make sure we see the new bounds from now on
sym.resetFlag(Provisional)
Expand Down
7 changes: 5 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -607,8 +607,11 @@ trait TypeAssigner {
def assignType(tree: untpd.ByNameTypeTree, result: Tree)(implicit ctx: Context): ByNameTypeTree =
tree.withType(ExprType(result.tpe))

def assignType(tree: untpd.TypeBoundsTree, lo: Tree, hi: Tree)(implicit ctx: Context): TypeBoundsTree =
tree.withType(if (lo eq hi) TypeAlias(lo.tpe) else TypeBounds(lo.tpe, hi.tpe))
def assignType(tree: untpd.TypeBoundsTree, lo: Tree, hi: Tree, alias: Tree)(implicit ctx: Context): TypeBoundsTree =
tree.withType(
if !alias.isEmpty then alias.tpe
else if lo eq hi then TypeAlias(lo.tpe)
else TypeBounds(lo.tpe, hi.tpe))

def assignType(tree: untpd.Bind, sym: Symbol)(implicit ctx: Context): Bind =
tree.withType(NamedType(NoPrefix, sym))
Expand Down
24 changes: 12 additions & 12 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -633,7 +633,7 @@ class Typer extends Namer

tpt1 match {
case AppliedTypeTree(_, targs) =>
for (targ @ TypeBoundsTree(_, _) <- targs)
for case targ: TypeBoundsTree <- targs do
ctx.error(WildcardOnTypeArgumentNotAllowedOnNew(), targ.sourcePos)
case _ =>
}
Expand Down Expand Up @@ -1445,7 +1445,7 @@ class Typer extends Namer
}
if (desugaredArg.isType)
arg match {
case TypeBoundsTree(EmptyTree, EmptyTree)
case TypeBoundsTree(EmptyTree, EmptyTree, _)
if tparam.paramInfo.isLambdaSub &&
tpt1.tpe.typeParamSymbols.nonEmpty &&
!ctx.mode.is(Mode.Pattern) =>
Expand All @@ -1464,7 +1464,7 @@ class Typer extends Namer
args.zipWithConserve(tparams)(typedArg(_, _)).asInstanceOf[List[Tree]]
}
val paramBounds = tparams.lazyZip(args).map {
case (tparam, TypeBoundsTree(EmptyTree, EmptyTree)) =>
case (tparam, TypeBoundsTree(EmptyTree, EmptyTree, _)) =>
// if type argument is a wildcard, suppress kind checking since
// there is no real argument.
NoType
Expand Down Expand Up @@ -1507,14 +1507,20 @@ class Typer extends Namer
}

def typedTypeBoundsTree(tree: untpd.TypeBoundsTree, pt: Type)(implicit ctx: Context): Tree = {
val TypeBoundsTree(lo, hi) = tree
val TypeBoundsTree(lo, hi, alias) = tree
val lo1 = typed(lo)
val hi1 = typed(hi)
val alias1 = typed(alias)

val lo2 = if (lo1.isEmpty) typed(untpd.TypeTree(defn.NothingType)) else lo1
val hi2 = if (hi1.isEmpty) typed(untpd.TypeTree(defn.AnyType)) else hi1

val tree1 = assignType(cpy.TypeBoundsTree(tree)(lo2, hi2), lo2, hi2)
if !alias1.isEmpty then
val bounds = TypeBounds(lo2.tpe, hi2.tpe)
if !bounds.contains(alias1.tpe) then
ctx.error(em"type ${alias1.tpe} outside bounds $bounds", tree.sourcePos)

val tree1 = assignType(cpy.TypeBoundsTree(tree)(lo2, hi2, alias1), lo2, hi2, alias1)
if (ctx.mode.is(Mode.Pattern))
// Associate a pattern-bound type symbol with the wildcard.
// The bounds of the type symbol can be constrained when comparing a pattern type
Expand Down Expand Up @@ -1951,13 +1957,7 @@ class Typer extends Namer
val arg1 = typed(tree.arg, pt)
if (ctx.mode is Mode.Type) {
if arg1.isType then
val result = assignType(cpy.Annotated(tree)(arg1, annot1), arg1, annot1)
result.tpe match {
case AnnotatedType(rhs, Annotation.WithBounds(bounds)) =>
if (!bounds.contains(rhs)) ctx.error(em"type $rhs outside bounds $bounds", tree.sourcePos)
case _ =>
}
result
assignType(cpy.Annotated(tree)(arg1, annot1), arg1, annot1)
else
assert(ctx.reporter.errorsReported)
TypeTree(UnspecifiedErrorType)
Expand Down
Loading