Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

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)

AltStyle によって変換されたページ (->オリジナル) /