Skip to content

Commit 4b32faa

Browse files
committed
Basic subtyping and solver for qualified types
1 parent f37debd commit 4b32faa

30 files changed

+899
-30
lines changed

compiler/src/dotty/tools/dotc/ast/tpd.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1437,6 +1437,19 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
14371437
def unapply(ts: List[Tree]): Option[List[Tree]] =
14381438
if ts.nonEmpty && ts.head.isType then Some(ts) else None
14391439

1440+
1441+
/** An extractor for trees that are constant values. */
1442+
object ConstantTree:
1443+
def unapply(tree: Tree)(using Context): Option[Constant] =
1444+
tree match
1445+
case Inlined(_, Nil, expr) => unapply(expr)
1446+
case Typed(expr, _) => unapply(expr)
1447+
case Literal(c) if c.tag == Constants.NullTag => Some(c)
1448+
case _ =>
1449+
tree.tpe.widenTermRefExpr.normalized.simplified match
1450+
case ConstantType(c) => Some(c)
1451+
case _ => None
1452+
14401453
/** Split argument clauses into a leading type argument clause if it exists and
14411454
* remaining clauses
14421455
*/

compiler/src/dotty/tools/dotc/config/Printers.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ object Printers {
5151
val overload = noPrinter
5252
val patmatch = noPrinter
5353
val pickling = noPrinter
54+
val qualifiedTypes = noPrinter
5455
val quotePickling = noPrinter
5556
val plugins = noPrinter
5657
val recheckr = noPrinter

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1036,6 +1036,7 @@ class Definitions {
10361036
@tu lazy val DeprecatedAnnot: ClassSymbol = requiredClass("scala.deprecated")
10371037
@tu lazy val DeprecatedOverridingAnnot: ClassSymbol = requiredClass("scala.deprecatedOverriding")
10381038
@tu lazy val DeprecatedInheritanceAnnot: ClassSymbol = requiredClass("scala.deprecatedInheritance")
1039+
@tu lazy val QualifiedAnnot: ClassSymbol = requiredClass("scala.annotation.qualified")
10391040
@tu lazy val ImplicitAmbiguousAnnot: ClassSymbol = requiredClass("scala.annotation.implicitAmbiguous")
10401041
@tu lazy val ImplicitNotFoundAnnot: ClassSymbol = requiredClass("scala.annotation.implicitNotFound")
10411042
@tu lazy val InferredDepFunAnnot: ClassSymbol = requiredClass("scala.caps.internal.inferredDepFun")

compiler/src/dotty/tools/dotc/core/TypeComparer.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import Capabilities.Capability
2727
import NameKinds.WildcardParamName
2828
import MatchTypes.isConcrete
2929
import scala.util.boundary, boundary.break
30+
import qualified_types.{QualifiedType, QualifiedTypes}
3031

3132
/** Provides methods to compare types.
3233
*/
@@ -884,6 +885,8 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
884885
println(i"assertion failed while compare captured $tp1 <:< $tp2")
885886
throw ex
886887
compareCapturing || fourthTry
888+
case QualifiedType(parent2, qualifier2) =>
889+
QualifiedTypes.typeImplies(tp1, qualifier2) && recur(tp1, parent2)
887890
case tp2: AnnotatedType if tp2.isRefining =>
888891
(tp1.derivesAnnotWith(tp2.annot.sameAnnotation) || tp1.isBottomType) &&
889892
recur(tp1, tp2.parent)
@@ -2110,7 +2113,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
21102113
* since `T >: Int` is subsumed by both alternatives in the first match clause.
21112114
*
21122115
* However, the following should not:
2113-
*
2116+
*
21142117
* def foo[T](e: Expr[T]): T = e match
21152118
* case I1(_) | B(_) => 42
21162119
*

compiler/src/dotty/tools/dotc/core/Types.scala

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ import compiletime.uninitialized
4141
import cc.*
4242
import CaptureSet.IdentityCaptRefMap
4343
import Capabilities.*
44-
44+
import qualified_types.QualifiedType
4545
import scala.annotation.internal.sharable
4646
import scala.annotation.threadUnsafe
4747

@@ -56,7 +56,7 @@ object Types extends TypeUtils {
5656
* The principal subclasses and sub-objects are as follows:
5757
*
5858
* ```none
59-
* Type -+- ProxyType --+- NamedType ----+--- TypeRef
59+
* Type -+- TypeProxy --+- NamedType ----+--- TypeRef
6060
* | | \
6161
* | +- SingletonType-+-+- TermRef
6262
* | | |
@@ -191,9 +191,10 @@ object Types extends TypeUtils {
191191

192192
/** Is this type a (possibly refined, applied, aliased or annotated) type reference
193193
* to the given type symbol?
194-
* @sym The symbol to compare to. It must be a class symbol or abstract type.
194+
* @param sym The symbol to compare to. It must be a class symbol or abstract type.
195195
* It makes no sense for it to be an alias type because isRef would always
196196
* return false in that case.
197+
* @param skipRefined If true, skip refinements, annotated types and applied types.
197198
*/
198199
def isRef(sym: Symbol, skipRefined: Boolean = true)(using Context): Boolean = this match {
199200
case this1: TypeRef =>
@@ -211,7 +212,7 @@ object Types extends TypeUtils {
211212
else this1.underlying.isRef(sym, skipRefined)
212213
case this1: TypeVar =>
213214
this1.instanceOpt.isRef(sym, skipRefined)
214-
case this1: AnnotatedType =>
215+
case this1: AnnotatedType if (!this1.isRefining || skipRefined) =>
215216
this1.parent.isRef(sym, skipRefined)
216217
case _ => false
217218
}
@@ -1567,6 +1568,7 @@ object Types extends TypeUtils {
15671568
def apply(tp: Type) = /*trace(i"deskolemize($tp) at $variance", show = true)*/
15681569
tp match {
15691570
case tp: SkolemType => range(defn.NothingType, atVariance(1)(apply(tp.info)))
1571+
case QualifiedType(_, _) => tp
15701572
case _ => mapOver(tp)
15711573
}
15721574
}
@@ -2102,7 +2104,7 @@ object Types extends TypeUtils {
21022104
/** Is `this` isomorphic to `that`, assuming pairs of matching binders `bs`?
21032105
* It is assumed that `this.ne(that)`.
21042106
*/
2105-
protected def iso(that: Any, bs: BinderPairs): Boolean = this.equals(that)
2107+
def iso(that: Any, bs: BinderPairs): Boolean = this.equals(that)
21062108

21072109
/** Equality used for hash-consing; uses `eq` on all recursive invocations,
21082110
* except where a BindingType is involved. The latter demand a deep isomorphism check.
@@ -3486,7 +3488,7 @@ object Types extends TypeUtils {
34863488
case _ => false
34873489
}
34883490

3489-
override protected def iso(that: Any, bs: BinderPairs) = that match
3491+
override def iso(that: Any, bs: BinderPairs) = that match
34903492
case that: AndType => tp1.equals(that.tp1, bs) && tp2.equals(that.tp2, bs)
34913493
case _ => false
34923494
}
@@ -3640,7 +3642,7 @@ object Types extends TypeUtils {
36403642
case _ => false
36413643
}
36423644

3643-
override protected def iso(that: Any, bs: BinderPairs) = that match
3645+
override def iso(that: Any, bs: BinderPairs) = that match
36443646
case that: OrType => tp1.equals(that.tp1, bs) && tp2.equals(that.tp2, bs) && isSoft == that.isSoft
36453647
case _ => false
36463648
}
@@ -4966,7 +4968,7 @@ object Types extends TypeUtils {
49664968
* anymore, or NoType if the variable can still be further constrained or a provisional
49674969
* instance type in the constraint can be retracted.
49684970
*/
4969-
private[core] def permanentInst = inst
4971+
def permanentInst = inst
49704972
private[core] def setPermanentInst(tp: Type): Unit =
49714973
inst = tp
49724974
if tp.exists && owningState != null then
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package dotty.tools.dotc.qualified_types
2+
3+
import dotty.tools.dotc.ast.tpd
4+
import dotty.tools.dotc.core.Annotations.Annotation
5+
import dotty.tools.dotc.core.Contexts.{ctx, Context}
6+
import dotty.tools.dotc.core.Types.{AnnotatedType, Type}
7+
8+
/** A qualified type is internally represented as a type annotated with a
9+
* `@qualified` annotation.
10+
*/
11+
object QualifiedType:
12+
/** Extractor for qualified types.
13+
*
14+
* @param tp
15+
* the type to deconstruct
16+
* @return
17+
* a pair containing the parent type and the qualifier tree (a lambda) on
18+
* success, [[None]] otherwise
19+
*/
20+
def unapply(tp: Type)(using Context): Option[(Type, tpd.Tree)] =
21+
tp match
22+
case AnnotatedType(parent, annot) if annot.symbol == ctx.definitions.QualifiedAnnot =>
23+
Some((parent, annot.argument(0).get))
24+
case _ =>
25+
None
26+
27+
/** Factory method to create a qualified type.
28+
*
29+
* @param parent
30+
* the parent type
31+
* @param qualifier
32+
* the qualifier tree (a lambda)
33+
* @return
34+
* a qualified type
35+
*/
36+
def apply(parent: Type, qualifier: tpd.Tree)(using Context): Type =
37+
val annotTp = ctx.definitions.QualifiedAnnot.typeRef.appliedTo(parent)
38+
AnnotatedType(parent, Annotation(tpd.New(annotTp, List(qualifier))))
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package dotty.tools.dotc.qualified_types
2+
3+
import dotty.tools.dotc.ast.tpd
4+
import dotty.tools.dotc.ast.tpd.{
5+
Apply,
6+
Block,
7+
EmptyTree,
8+
Ident,
9+
If,
10+
Lambda,
11+
Literal,
12+
New,
13+
Select,
14+
SeqLiteral,
15+
This,
16+
Throw,
17+
Tree,
18+
TypeApply,
19+
Typed,
20+
given
21+
}
22+
import dotty.tools.dotc.core.Atoms
23+
import dotty.tools.dotc.core.Constants.Constant
24+
import dotty.tools.dotc.core.Contexts.{ctx, Context}
25+
import dotty.tools.dotc.core.Decorators.{i, em, toTermName}
26+
import dotty.tools.dotc.core.StdNames.nme
27+
import dotty.tools.dotc.core.Symbols.{defn, Symbol}
28+
import dotty.tools.dotc.core.Types.{AndType, ConstantType, SkolemType, ErrorType, MethodType, OrType, TermRef, Type, TypeProxy}
29+
30+
import dotty.tools.dotc.report
31+
import dotty.tools.dotc.reporting.trace
32+
import dotty.tools.dotc.config.Printers
33+
34+
object QualifiedTypes:
35+
/** Does the type `tp1` imply the qualifier `qualifier2`?
36+
*
37+
* Used by [[dotty.tools.dotc.core.TypeComparer]] to compare qualified types.
38+
*
39+
* Note: the logic here is similar to [[Type#derivesAnnotWith]] but
40+
* additionally handle comparisons with [[SingletonType]]s.
41+
*/
42+
def typeImplies(tp1: Type, qualifier2: Tree)(using Context): Boolean =
43+
trace(i"typeImplies $tp1 --> $qualifier2", Printers.qualifiedTypes):
44+
tp1 match
45+
case QualifiedType(parent1, qualifier1) =>
46+
QualifierSolver().implies(qualifier1, qualifier2)
47+
case tp1: (ConstantType | TermRef) =>
48+
QualifierSolver().implies(equalToPredicate(tpd.singleton(tp1)), qualifier2)
49+
|| typeImplies(tp1.underlying, qualifier2)
50+
case tp1: TypeProxy =>
51+
typeImplies(tp1.underlying, qualifier2)
52+
case AndType(tp11, tp12) =>
53+
typeImplies(tp11, qualifier2) || typeImplies(tp12, qualifier2)
54+
case OrType(tp11, tp12) =>
55+
typeImplies(tp11, qualifier2) && typeImplies(tp12, qualifier2)
56+
case _ =>
57+
false
58+
// QualifierSolver().implies(truePredicate(), qualifier2)
59+
60+
/** Try to adapt the tree to the given type `pt`
61+
*
62+
* Returns [[EmptyTree]] if `pt` does not contain qualifiers or if the tree
63+
* cannot be adapted, or the adapted tree otherwise.
64+
*
65+
* Used by [[dotty.tools.dotc.core.Typer]].
66+
*/
67+
def adapt(tree: Tree, pt: Type)(using Context): Tree =
68+
trace(i"adapt $tree to $pt", Printers.qualifiedTypes):
69+
if containsQualifier(pt) && isSimple(tree) then
70+
val selfifiedTp = QualifiedType(tree.tpe, equalToPredicate(tree))
71+
if selfifiedTp <:< pt then tree.cast(selfifiedTp) else EmptyTree
72+
else
73+
EmptyTree
74+
75+
def isSimple(tree: Tree)(using Context): Boolean =
76+
tree match
77+
case Apply(fn, args) => isSimple(fn) && args.forall(isSimple)
78+
case TypeApply(fn, args) => isSimple(fn)
79+
case SeqLiteral(elems, _) => elems.forall(isSimple)
80+
case Typed(expr, _) => isSimple(expr)
81+
case Block(Nil, expr) => isSimple(expr)
82+
case _ => tpd.isIdempotentExpr(tree)
83+
84+
def containsQualifier(tp: Type)(using Context): Boolean =
85+
tp match
86+
case QualifiedType(_, _) => true
87+
case tp: TypeProxy => containsQualifier(tp.underlying)
88+
case AndType(tp1, tp2) => containsQualifier(tp1) || containsQualifier(tp2)
89+
case OrType(tp1, tp2) => containsQualifier(tp1) || containsQualifier(tp2)
90+
case _ => false
91+
92+
def checkContainsSkolem(tp: Type)(using Context): Boolean =
93+
var res = true
94+
tp.foreachPart:
95+
case QualifiedType(_, qualifier) =>
96+
qualifier.foreachSubTree: subTree =>
97+
subTree.tpe.foreachPart:
98+
case tp: SkolemType =>
99+
report.error(em"The qualified type $qualifier cannot be checked at runtime", qualifier.srcPos)
100+
res = false
101+
case _ => ()
102+
case _ => ()
103+
res
104+
105+
private def equalToPredicate(tree: Tree)(using Context): Tree =
106+
Lambda(
107+
MethodType(List("v".toTermName))(_ => List(tree.tpe), _ => defn.BooleanType),
108+
(args) => Ident(args(0).symbol.termRef).equal(tree)
109+
)
110+
111+
private def truePredicate()(using Context): Tree =
112+
Lambda(
113+
MethodType(List("v".toTermName))(_ => List(defn.AnyType), _ => defn.BooleanType),
114+
(args) => Literal(Constant(true))
115+
)

0 commit comments

Comments
 (0)