Skip to content

Commit 6045a23

Browse files
mbovelSporarum
andcommitted
Add syntax for qualified types
Co-Authored-By: Quentin Bernet <28290641+Sporarum@users.noreply.github.com>
1 parent c9b9aea commit 6045a23

File tree

11 files changed

+300
-5
lines changed

11 files changed

+300
-5
lines changed

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

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import Symbols.*, StdNames.*, Trees.*, ContextOps.*
88
import Decorators.*
99
import Annotations.Annotation
1010
import NameKinds.{UniqueName, ContextBoundParamName, ContextFunctionParamName, DefaultGetterName, WildcardParamName}
11-
import typer.{Namer, Checking}
11+
import typer.{Namer, Checking, ErrorReporting}
1212
import util.{Property, SourceFile, SourcePosition, SrcPos, Chars}
1313
import config.{Feature, Config}
1414
import config.Feature.{sourceVersion, migrateTo3, enabled, betterForsEnabled}
@@ -199,9 +199,10 @@ object desugar {
199199
def valDef(vdef0: ValDef)(using Context): Tree =
200200
val vdef @ ValDef(_, tpt, rhs) = vdef0
201201
val valName = normalizeName(vdef, tpt).asTermName
202+
val tpt1 = qualifiedType(tpt, valName)
202203
var mods1 = vdef.mods
203204

204-
val vdef1 = cpy.ValDef(vdef)(name = valName).withMods(mods1)
205+
val vdef1 = cpy.ValDef(vdef)(name = valName, tpt = tpt1).withMods(mods1)
205206

206207
if isSetterNeeded(vdef) then
207208
val setterParam = makeSyntheticParameter(tpt = SetterParamTree().watching(vdef))
@@ -2145,6 +2146,10 @@ object desugar {
21452146
case PatDef(mods, pats, tpt, rhs) =>
21462147
val pats1 = if (tpt.isEmpty) pats else pats map (Typed(_, tpt))
21472148
flatTree(pats1 map (makePatDef(tree, mods, _, rhs)))
2149+
case QualifiedTypeTree(parent, None, qualifier) =>
2150+
ErrorReporting.errorTree(parent, em"missing parameter name in qualified type", tree.srcPos)
2151+
case QualifiedTypeTree(parent, Some(paramName), qualifier) =>
2152+
qualifiedType(parent, paramName, qualifier, tree.span)
21482153
case ext: ExtMethods =>
21492154
Block(List(ext), syntheticUnitLiteral.withSpan(ext.span))
21502155
case f: FunctionWithMods if f.hasErasedParams => makeFunctionWithValDefs(f, pt)
@@ -2323,4 +2328,23 @@ object desugar {
23232328
collect(tree)
23242329
buf.toList
23252330
}
2331+
2332+
/** If `tree` is a `QualifiedTypeTree`, then desugars it using `paramName` as
2333+
* the qualified paramater name. Otherwise, returns `tree` unchanged.
2334+
*/
2335+
def qualifiedType(tree: Tree, paramName: TermName)(using Context): Tree = tree match
2336+
case QualifiedTypeTree(parent, None, qualifier) => qualifiedType(parent, paramName, qualifier, tree.span)
2337+
case _ => tree
2338+
2339+
/** Returns the annotated type used to represent the qualified type with the
2340+
* given components:
2341+
* `parent @qualified[parent]((paramName: parent) => qualifier)`.
2342+
*/
2343+
def qualifiedType(parent: Tree, paramName: TermName, qualifier: Tree, span: Span)(using Context): Tree =
2344+
val param = makeParameter(paramName, parent, EmptyModifiers) // paramName: parent
2345+
val predicate = WildcardFunction(List(param), qualifier) // (paramName: parent) => qualifier
2346+
val qualifiedAnnot = scalaAnnotationDot(nme.qualified)
2347+
val annot = Apply(TypeApply(qualifiedAnnot, List(parent)), predicate).withSpan(span) // @qualified[parent](predicate)
2348+
Annotated(parent, annot).withSpan(span) // parent @qualified[parent](predicate)
2349+
23262350
}

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,9 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
156156
*/
157157
case class CapturesAndResult(refs: List[Tree], parent: Tree)(implicit @constructorOnly src: SourceFile) extends TypTree
158158

159+
/** { x: T with p }  (only relevant under qualifiedTypes) */
160+
case class QualifiedTypeTree(parent: Tree, paramName: Option[TermName], qualifier: Tree)(implicit @constructorOnly src: SourceFile) extends TypTree
161+
159162
/** A type tree appearing somewhere in the untyped DefDef of a lambda, it will be typed using `tpFun`.
160163
*
161164
* @param isResult Is this the result type of the lambda? This is handled specially in `Namer#valOrDefDefSig`.
@@ -704,6 +707,10 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
704707
case tree: CapturesAndResult if (refs eq tree.refs) && (parent eq tree.parent) => tree
705708
case _ => finalize(tree, untpd.CapturesAndResult(refs, parent))
706709

710+
def QualifiedTypeTree(tree: Tree)(parent: Tree, paramName: Option[TermName], qualifier: Tree)(using Context): Tree = tree match
711+
case tree: QualifiedTypeTree if (parent eq tree.parent) && (paramName eq tree.paramName) && (qualifier eq tree.qualifier) => tree
712+
case _ => finalize(tree, untpd.QualifiedTypeTree(parent, paramName, qualifier)(tree.source))
713+
707714
def TypedSplice(tree: Tree)(splice: tpd.Tree)(using Context): ProxyTree = tree match {
708715
case tree: TypedSplice if splice `eq` tree.splice => tree
709716
case _ => finalize(tree, untpd.TypedSplice(splice)(using ctx))
@@ -767,6 +774,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
767774
cpy.MacroTree(tree)(transform(expr))
768775
case CapturesAndResult(refs, parent) =>
769776
cpy.CapturesAndResult(tree)(transform(refs), transform(parent))
777+
case QualifiedTypeTree(parent, paramName, qualifier) =>
778+
cpy.QualifiedTypeTree(tree)(transform(parent), paramName, transform(qualifier))
770779
case _ =>
771780
super.transformMoreCases(tree)
772781
}
@@ -826,6 +835,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
826835
this(x, expr)
827836
case CapturesAndResult(refs, parent) =>
828837
this(this(x, refs), parent)
838+
case QualifiedTypeTree(parent, paramName, qualifier) =>
839+
this(this(x, parent), qualifier)
829840
case _ =>
830841
super.foldMoreCases(x, tree)
831842
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ object Feature:
3333
val clauseInterleaving = experimental("clauseInterleaving")
3434
val pureFunctions = experimental("pureFunctions")
3535
val captureChecking = experimental("captureChecking")
36+
val qualifiedTypes = experimental("qualifiedTypes")
3637
val into = experimental("into")
3738
val namedTuples = experimental("namedTuples")
3839
val modularity = experimental("modularity")
@@ -65,6 +66,7 @@ object Feature:
6566
(clauseInterleaving, "Enable clause interleaving"),
6667
(pureFunctions, "Enable pure functions for capture checking"),
6768
(captureChecking, "Enable experimental capture checking"),
69+
(qualifiedTypes, "Enable experimental qualified types"),
6870
(into, "Allow into modifier on parameter types"),
6971
(namedTuples, "Allow named tuples"),
7072
(modularity, "Enable experimental modularity features"),
@@ -158,6 +160,10 @@ object Feature:
158160
if ctx.run != null then ctx.run.nn.ccEnabledSomewhere
159161
else enabledBySetting(captureChecking)
160162

163+
/** Is qualifiedTypes enabled for this compilation unit? */
164+
def qualifiedTypesEnabled(using Context) =
165+
enabledBySetting(qualifiedTypes)
166+
161167
def sourceVersionSetting(using Context): SourceVersion =
162168
SourceVersion.valueOf(ctx.settings.source.value)
163169

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,7 @@ object StdNames {
585585
val productElementName: N = "productElementName"
586586
val productIterator: N = "productIterator"
587587
val productPrefix: N = "productPrefix"
588+
val qualified : N = "qualified"
588589
val quotes : N = "quotes"
589590
val raw_ : N = "raw"
590591
val refl: N = "refl"

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,13 @@ object Parsers {
445445
finally inMatchPattern = saved
446446
}
447447

448+
private var inQualifiedType = false
449+
private def fromWithinQualifiedType[T](body: => T): T =
450+
val saved = inQualifiedType
451+
inQualifiedType = true
452+
try body
453+
finally inQualifiedType = saved
454+
448455
private var staged = StageKind.None
449456
def withinStaged[T](kind: StageKind)(op: => T): T = {
450457
val saved = staged
@@ -1085,6 +1092,25 @@ object Parsers {
10851092
|| in.lookahead.token == EOF // important for REPL completions
10861093
|| ctx.mode.is(Mode.Interactive) // in interactive mode the next tokens might be missing
10871094

1095+
/** Under `qualifiedTypes` language import: is the token sequence following
1096+
* the current `{` classified as a qualified type? This is the case if the
1097+
* next token is an `IDENT`, followed by `:`.
1098+
*/
1099+
def followingIsQualifiedType(): Boolean =
1100+
in.featureEnabled(Feature.qualifiedTypes) && {
1101+
val lookahead = in.LookaheadScanner(allowIndent = true)
1102+
1103+
if in.token == INDENT then
1104+
() // The LookaheadScanner doesn't see previous indents, so no need to skip it
1105+
else
1106+
lookahead.nextToken() // skips the opening brace
1107+
1108+
lookahead.token == IDENTIFIER && {
1109+
lookahead.nextToken()
1110+
lookahead.token == COLONfollow
1111+
}
1112+
}
1113+
10881114
/* --------- OPERAND/OPERATOR STACK --------------------------------------- */
10891115

10901116
var opStack: List[OpInfo] = Nil
@@ -1872,12 +1898,22 @@ object Parsers {
18721898
t
18731899
}
18741900

1875-
/** WithType ::= AnnotType {`with' AnnotType} (deprecated)
1876-
*/
1901+
/** With qualifiedTypes enabled:
1902+
* WithType ::= AnnotType [`with' PostfixExpr]
1903+
*
1904+
* Otherwise:
1905+
* WithType ::= AnnotType {`with' AnnotType} (deprecated)
1906+
*/
18771907
def withType(): Tree = withTypeRest(annotType())
18781908

18791909
def withTypeRest(t: Tree): Tree =
1880-
if in.token == WITH then
1910+
if Feature.qualifiedTypesEnabled && in.token == WITH then
1911+
if inQualifiedType then t
1912+
else
1913+
in.nextToken()
1914+
val qualifier = postfixExpr()
1915+
QualifiedTypeTree(t, None, qualifier).withSpan(Span(t.span.start, qualifier.span.end))
1916+
else if in.token == WITH then
18811917
val withOffset = in.offset
18821918
in.nextToken()
18831919
if in.token == LBRACE || in.token == INDENT then
@@ -2025,6 +2061,7 @@ object Parsers {
20252061
* | ‘(’ ArgTypes ‘)’
20262062
* | ‘(’ NamesAndTypes ‘)’
20272063
* | Refinement
2064+
* | QualifiedType -- under qualifiedTypes
20282065
* | TypeSplice -- deprecated syntax (since 3.0.0)
20292066
* | SimpleType1 TypeArgs
20302067
* | SimpleType1 `#' id
@@ -2034,6 +2071,8 @@ object Parsers {
20342071
atSpan(in.offset) {
20352072
makeTupleOrParens(inParensWithCommas(argTypes(namedOK = false, wildOK = true, tupleOK = true)))
20362073
}
2074+
else if in.token == LBRACE && followingIsQualifiedType() then
2075+
qualifiedType()
20372076
else if in.token == LBRACE then
20382077
atSpan(in.offset) { RefinedTypeTree(EmptyTree, refinement(indentOK = false)) }
20392078
else if (isSplice)
@@ -2198,6 +2237,19 @@ object Parsers {
21982237
else
21992238
inBraces(refineStatSeq())
22002239

2240+
/** QualifiedType ::= `{` Ident `:` Type `with` Block `}`
2241+
*/
2242+
def qualifiedType(): Tree =
2243+
val startOffset = in.offset
2244+
accept(LBRACE)
2245+
val id = ident()
2246+
accept(COLONfollow)
2247+
val tp = fromWithinQualifiedType(typ())
2248+
accept(WITH)
2249+
val qualifier = block(simplify = true)
2250+
accept(RBRACE)
2251+
QualifiedTypeTree(tp, Some(id), qualifier).withSpan(Span(startOffset, qualifier.span.end))
2252+
22012253
/** TypeBounds ::= [`>:' Type] [`<:' Type]
22022254
* | `^` -- under captureChecking
22032255
*/

compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -808,6 +808,10 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
808808
prefix ~~ idx.toString ~~ "|" ~~ tpeText ~~ "|" ~~ argsText ~~ "|" ~~ contentText ~~ postfix
809809
case CapturesAndResult(refs, parent) =>
810810
changePrec(GlobalPrec)("^{" ~ Text(refs.map(toText), ", ") ~ "}" ~ toText(parent))
811+
case QualifiedTypeTree(parent, paramName, predicate) =>
812+
paramName match
813+
case Some(name) => "{" ~ toText(name) ~ ": " ~ toText(parent) ~ " with " ~ toText(predicate) ~ "}"
814+
case None => toText(parent) ~ " with " ~ toText(predicate)
811815
case ContextBoundTypeTree(tycon, pname, ownName) =>
812816
toText(pname) ~ " : " ~ toText(tycon) ~ (" as " ~ toText(ownName) `provided` !ownName.isEmpty)
813817
case _ =>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package scala.annotation
2+
3+
/** Annotation for qualified types. */
4+
@experimental class qualified[T](predicate: T => Boolean) extends StaticAnnotation

library/src/scala/runtime/stdLibPatches/language.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ object language:
8484
@compileTimeOnly("`captureChecking` can only be used at compile time in import statements")
8585
object captureChecking
8686

87+
/** Experimental support for qualified types */
88+
@compileTimeOnly("`qualifiedTypes` can only be used at compile time in import statements")
89+
object qualifiedTypes
90+
8791
/** Experimental support for automatic conversions of arguments, without requiring
8892
* a language import `import scala.language.implicitConversions`.
8993
*

0 commit comments

Comments
 (0)