Skip to content

Commit d5021f8

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

File tree

14 files changed

+385
-7
lines changed

14 files changed

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

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,13 @@ 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: parent with qualifier }` if `paramName == Some(x)`,
159+
* `parent with qualifier` otherwise.
160+
*
161+
* Only relevant under `qualifiedTypes`.
162+
*/
163+
case class QualifiedTypeTree(parent: Tree, paramName: Option[TermName], qualifier: Tree)(implicit @constructorOnly src: SourceFile) extends TypTree
164+
158165
/** A type tree appearing somewhere in the untyped DefDef of a lambda, it will be typed using `tpFun`.
159166
*
160167
* @param isResult Is this the result type of the lambda? This is handled specially in `Namer#valOrDefDefSig`.
@@ -703,6 +710,10 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
703710
case tree: CapturesAndResult if (refs eq tree.refs) && (parent eq tree.parent) => tree
704711
case _ => finalize(tree, untpd.CapturesAndResult(refs, parent))
705712

713+
def QualifiedTypeTree(tree: Tree)(parent: Tree, paramName: Option[TermName], qualifier: Tree)(using Context): Tree = tree match
714+
case tree: QualifiedTypeTree if (parent eq tree.parent) && (paramName eq tree.paramName) && (qualifier eq tree.qualifier) => tree
715+
case _ => finalize(tree, untpd.QualifiedTypeTree(parent, paramName, qualifier)(tree.source))
716+
706717
def TypedSplice(tree: Tree)(splice: tpd.Tree)(using Context): ProxyTree = tree match {
707718
case tree: TypedSplice if splice `eq` tree.splice => tree
708719
case _ => finalize(tree, untpd.TypedSplice(splice)(using ctx))
@@ -766,6 +777,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
766777
cpy.MacroTree(tree)(transform(expr))
767778
case CapturesAndResult(refs, parent) =>
768779
cpy.CapturesAndResult(tree)(transform(refs), transform(parent))
780+
case QualifiedTypeTree(parent, paramName, qualifier) =>
781+
cpy.QualifiedTypeTree(tree)(transform(parent), paramName, transform(qualifier))
769782
case _ =>
770783
super.transformMoreCases(tree)
771784
}
@@ -825,6 +838,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
825838
this(x, expr)
826839
case CapturesAndResult(refs, parent) =>
827840
this(this(x, refs), parent)
841+
case QualifiedTypeTree(parent, paramName, qualifier) =>
842+
this(this(x, parent), qualifier)
828843
case _ =>
829844
super.foldMoreCases(x, tree)
830845
}

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: 38 additions & 4 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
@@ -1879,12 +1886,22 @@ object Parsers {
18791886
t
18801887
}
18811888

1882-
/** WithType ::= AnnotType {`with' AnnotType} (deprecated)
1883-
*/
1889+
/** With qualifiedTypes enabled:
1890+
* WithType ::= AnnotType [`with' PostfixExpr]
1891+
*
1892+
* Otherwise:
1893+
* WithType ::= AnnotType {`with' AnnotType} (deprecated)
1894+
*/
18841895
def withType(): Tree = withTypeRest(annotType())
18851896

18861897
def withTypeRest(t: Tree): Tree =
1887-
if in.token == WITH then
1898+
if in.featureEnabled(Feature.qualifiedTypes) && in.token == WITH then
1899+
if inQualifiedType then t
1900+
else
1901+
in.nextToken()
1902+
val qualifier = postfixExpr()
1903+
QualifiedTypeTree(t, None, qualifier).withSpan(Span(t.span.start, qualifier.span.end))
1904+
else if in.token == WITH then
18881905
val withOffset = in.offset
18891906
in.nextToken()
18901907
if in.token == LBRACE || in.token == INDENT then
@@ -2032,6 +2049,7 @@ object Parsers {
20322049
* | ‘(’ ArgTypes ‘)’
20332050
* | ‘(’ NamesAndTypes ‘)’
20342051
* | Refinement
2052+
* | QualifiedType -- under qualifiedTypes
20352053
* | TypeSplice -- deprecated syntax (since 3.0.0)
20362054
* | SimpleType1 TypeArgs
20372055
* | SimpleType1 `#' id
@@ -2042,7 +2060,10 @@ object Parsers {
20422060
makeTupleOrParens(inParensWithCommas(argTypes(namedOK = false, wildOK = true, tupleOK = true)))
20432061
}
20442062
else if in.token == LBRACE then
2045-
atSpan(in.offset) { RefinedTypeTree(EmptyTree, refinement(indentOK = false)) }
2063+
if in.featureEnabled(Feature.qualifiedTypes) && in.lookahead.token == IDENTIFIER then
2064+
qualifiedType()
2065+
else
2066+
atSpan(in.offset) { RefinedTypeTree(EmptyTree, refinement(indentOK = false)) }
20462067
else if (isSplice)
20472068
splice(isType = true)
20482069
else
@@ -2205,6 +2226,19 @@ object Parsers {
22052226
else
22062227
inBraces(refineStatSeq())
22072228

2229+
/** QualifiedType ::= `{` Ident `:` Type `with` Block `}`
2230+
*/
2231+
def qualifiedType(): Tree =
2232+
val startOffset = in.offset
2233+
accept(LBRACE)
2234+
val id = ident()
2235+
accept(COLONfollow)
2236+
val tp = fromWithinQualifiedType(typ())
2237+
accept(WITH)
2238+
val qualifier = block(simplify = true)
2239+
accept(RBRACE)
2240+
QualifiedTypeTree(tp, Some(id), qualifier).withSpan(Span(startOffset, qualifier.span.end))
2241+
22082242
/** TypeBounds ::= [`>:' Type] [`<:' Type]
22092243
* | `^` -- under captureChecking
22102244
*/

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -814,6 +814,10 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
814814
prefix ~~ idx.toString ~~ "|" ~~ tpeText ~~ "|" ~~ argsText ~~ "|" ~~ contentText ~~ postfix
815815
case CapturesAndResult(refs, parent) =>
816816
changePrec(GlobalPrec)("^{" ~ Text(refs.map(toText), ", ") ~ "}" ~ toText(parent))
817+
case QualifiedTypeTree(parent, paramName, predicate) =>
818+
paramName match
819+
case Some(name) => "{" ~ toText(name) ~ ": " ~ toText(parent) ~ " with " ~ toText(predicate) ~ "}"
820+
case None => toText(parent) ~ " with " ~ toText(predicate)
817821
case ContextBoundTypeTree(tycon, pname, ownName) =>
818822
toText(pname) ~ " : " ~ toText(tycon) ~ (" as " ~ toText(ownName) `provided` !ownName.isEmpty)
819823
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)