Skip to content

Commit b779cd1

Browse files
mbovelSporarum
andcommitted
Add syntax for qualified types
Co-Authored-By: Quentin Bernet <[email protected]>
1 parent 2fc299b commit b779cd1

File tree

14 files changed

+308
-6
lines changed

14 files changed

+308
-6
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))
@@ -2157,6 +2158,10 @@ object desugar {
21572158
case PatDef(mods, pats, tpt, rhs) =>
21582159
val pats1 = if (tpt.isEmpty) pats else pats map (Typed(_, tpt))
21592160
flatTree(pats1 map (makePatDef(tree, mods, _, rhs)))
2161+
case QualifiedTypeTree(parent, None, qualifier) =>
2162+
ErrorReporting.errorTree(parent, em"missing parameter name in qualified type", tree.srcPos)
2163+
case QualifiedTypeTree(parent, Some(paramName), qualifier) =>
2164+
qualifiedType(parent, paramName, qualifier, tree.span)
21602165
case ext: ExtMethods =>
21612166
Block(List(ext), syntheticUnitLiteral.withSpan(ext.span))
21622167
case f: FunctionWithMods if f.hasErasedParams => makeFunctionWithValDefs(f, pt)
@@ -2335,4 +2340,23 @@ object desugar {
23352340
collect(tree)
23362341
buf.toList
23372342
}
2343+
2344+
/** If `tree` is a `QualifiedTypeTree`, then desugars it using `paramName` as
2345+
* the qualified parameter name. Otherwise, returns `tree` unchanged.
2346+
*/
2347+
def qualifiedType(tree: Tree, paramName: TermName)(using Context): Tree = tree match
2348+
case QualifiedTypeTree(parent, None, qualifier) => qualifiedType(parent, paramName, qualifier, tree.span)
2349+
case _ => tree
2350+
2351+
/** Returns the annotated type used to represent the qualified type with the
2352+
* given components:
2353+
* `parent @qualified[parent]((paramName: parent) => qualifier)`.
2354+
*/
2355+
def qualifiedType(parent: Tree, paramName: TermName, qualifier: Tree, span: Span)(using Context): Tree =
2356+
val param = makeParameter(paramName, parent, EmptyModifiers) // paramName: parent
2357+
val predicate = WildcardFunction(List(param), qualifier) // (paramName: parent) => qualifier
2358+
val qualifiedAnnot = scalaAnnotationDot(nme.qualified)
2359+
val annot = Apply(TypeApply(qualifiedAnnot, List(parent)), predicate).withSpan(span) // @qualified[parent](predicate)
2360+
Annotated(parent, annot).withSpan(span) // parent @qualified[parent](predicate)
2361+
23382362
}

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

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

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

709+
def QualifiedTypeTree(tree: Tree)(parent: Tree, paramName: Option[TermName], qualifier: Tree)(using Context): Tree = tree match
710+
case tree: QualifiedTypeTree if (parent eq tree.parent) && (paramName eq tree.paramName) && (qualifier eq tree.qualifier) => tree
711+
case _ => finalize(tree, untpd.QualifiedTypeTree(parent, paramName, qualifier)(tree.source))
712+
706713
def TypedSplice(tree: Tree)(splice: tpd.Tree)(using Context): ProxyTree = tree match {
707714
case tree: TypedSplice if splice `eq` tree.splice => tree
708715
case _ => finalize(tree, untpd.TypedSplice(splice)(using ctx))
@@ -766,6 +773,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
766773
cpy.MacroTree(tree)(transform(expr))
767774
case CapturesAndResult(refs, parent) =>
768775
cpy.CapturesAndResult(tree)(transform(refs), transform(parent))
776+
case QualifiedTypeTree(parent, paramName, qualifier) =>
777+
cpy.QualifiedTypeTree(tree)(transform(parent), paramName, transform(qualifier))
769778
case _ =>
770779
super.transformMoreCases(tree)
771780
}
@@ -825,6 +834,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
825834
this(x, expr)
826835
case CapturesAndResult(refs, parent) =>
827836
this(this(x, refs), parent)
837+
case QualifiedTypeTree(parent, paramName, qualifier) =>
838+
this(this(x, parent), qualifier)
828839
case _ =>
829840
super.foldMoreCases(x, tree)
830841
}

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 modularity = experimental("modularity")
3839
val betterMatchTypeExtractors = experimental("betterMatchTypeExtractors")
@@ -64,6 +65,7 @@ object Feature:
6465
(clauseInterleaving, "Enable clause interleaving"),
6566
(pureFunctions, "Enable pure functions for capture checking"),
6667
(captureChecking, "Enable experimental capture checking"),
68+
(qualifiedTypes, "Enable experimental qualified types"),
6769
(into, "Allow into modifier on parameter types"),
6870
(modularity, "Enable experimental modularity features"),
6971
(betterMatchTypeExtractors, "Enable better match type extractors"),
@@ -156,6 +158,10 @@ object Feature:
156158
if ctx.run != null then ctx.run.nn.ccEnabledSomewhere
157159
else enabledBySetting(captureChecking)
158160

161+
/** Is qualifiedTypes enabled for this compilation unit? */
162+
def qualifiedTypesEnabled(using Context) =
163+
enabledBySetting(qualifiedTypes)
164+
159165
def sourceVersionSetting(using Context): SourceVersion =
160166
SourceVersion.valueOf(ctx.settings.source.value)
161167

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 in.featureEnabled(Feature.qualifiedTypes) && 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
@@ -810,6 +810,10 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
810810
prefix ~~ idx.toString ~~ "|" ~~ tpeText ~~ "|" ~~ argsText ~~ "|" ~~ contentText ~~ postfix
811811
case CapturesAndResult(refs, parent) =>
812812
changePrec(GlobalPrec)("^{" ~ Text(refs.map(toText), ", ") ~ "}" ~ toText(parent))
813+
case QualifiedTypeTree(parent, paramName, predicate) =>
814+
paramName match
815+
case Some(name) => "{" ~ toText(name) ~ ": " ~ toText(parent) ~ " with " ~ toText(predicate) ~ "}"
816+
case None => toText(parent) ~ " with " ~ toText(predicate)
813817
case ContextBoundTypeTree(tycon, pname, ownName) =>
814818
toText(pname) ~ " : " ~ toText(tycon) ~ (" as " ~ toText(ownName) `provided` !ownName.isEmpty)
815819
case _ =>

compiler/src/dotty/tools/dotc/typer/ImportInfo.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ class ImportInfo(symf: Context ?=> Symbol,
206206

207207
/** Does this import clause or a preceding import clause enable `feature`?
208208
*
209-
* @param feature a possibly quailified name, e.g.
209+
* @param feature a possibly qualified name, e.g.
210210
* strictEquality
211211
* experimental.genericNumberLiterals
212212
*
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
*

project/MiMaFilters.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ object MiMaFilters {
1212
ProblemFilters.exclude[MissingClassProblem]("scala.runtime.stdLibPatches.language$experimental$betterFors$"),
1313
ProblemFilters.exclude[MissingFieldProblem]("scala.runtime.stdLibPatches.language#experimental.quotedPatternsWithPolymorphicFunctions"),
1414
ProblemFilters.exclude[MissingClassProblem]("scala.runtime.stdLibPatches.language$experimental$quotedPatternsWithPolymorphicFunctions$"),
15+
ProblemFilters.exclude[MissingFieldProblem]("scala.runtime.stdLibPatches.language#experimental.qualifiedTypes"),
16+
ProblemFilters.exclude[MissingClassProblem]("scala.runtime.stdLibPatches.language$experimental$qualifiedTypes$"),
1517
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.runtime.Patterns.higherOrderHoleWithTypes"),
1618
),
1719

@@ -72,6 +74,8 @@ object MiMaFilters {
7274
ProblemFilters.exclude[MissingClassProblem]("scala.runtime.stdLibPatches.language$experimental$betterMatchTypeExtractors$"),
7375
ProblemFilters.exclude[MissingClassProblem]("scala.runtime.stdLibPatches.language$experimental$modularity$"),
7476
ProblemFilters.exclude[MissingClassProblem]("scala.runtime.stdLibPatches.language$experimental$namedTuples$"),
77+
ProblemFilters.exclude[MissingFieldProblem]("scala.runtime.stdLibPatches.language#experimental.qualifiedTypes"),
78+
ProblemFilters.exclude[MissingClassProblem]("scala.runtime.stdLibPatches.language$experimental$qualifiedTypes$"),
7579
ProblemFilters.exclude[MissingFieldProblem]("scala.runtime.stdLibPatches.language.3.7-migration"),
7680
ProblemFilters.exclude[MissingFieldProblem]("scala.runtime.stdLibPatches.language.3.7"),
7781
ProblemFilters.exclude[MissingClassProblem]("scala.runtime.stdLibPatches.language$3$u002E7$"),

0 commit comments

Comments
 (0)