Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 30 additions & 10 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1451,16 +1451,36 @@ object desugar {
* (t) ==> t
* (t1, ..., tN) ==> TupleN(t1, ..., tN)
*/
def smallTuple(tree: Tuple)(using Context): Tree = {
val ts = tree.trees
val arity = ts.length
assert(arity <= Definitions.MaxTupleArity)
def tupleTypeRef = defn.TupleType(arity).nn
if (arity == 0)
if (ctx.mode is Mode.Type) TypeTree(defn.UnitType) else unitLiteral
else if (ctx.mode is Mode.Type) AppliedTypeTree(ref(tupleTypeRef), ts)
else Apply(ref(tupleTypeRef.classSymbol.companionModule.termRef), ts)
}
def tuple(tree: Tuple)(using Context): Tree =
val elems = tree.trees.mapConserve(desugarTupleElem)
val arity = elems.length
if arity <= Definitions.MaxTupleArity then
def tupleTypeRef = defn.TupleType(arity).nn
val tree1 =
if arity == 0 then
if ctx.mode is Mode.Type then TypeTree(defn.UnitType) else unitLiteral
else if ctx.mode is Mode.Type then AppliedTypeTree(ref(tupleTypeRef), elems)
else Apply(ref(tupleTypeRef.classSymbol.companionModule.termRef), elems)
tree1.withSpan(tree.span)
else
cpy.Tuple(tree)(elems)

private def desugarTupleElem(elem: untpd.Tree)(using Context): untpd.Tree = elem match
case NamedArg(name, arg) =>
val nameLit = untpd.Literal(Constant(name.toString))
if ctx.mode.is(Mode.Type) then
untpd.AppliedTypeTree(untpd.ref(defn.Tuple_NamedValueType),
untpd.SingletonTypeTree(nameLit) :: arg :: Nil)
else if ctx.mode.is(Mode.Pattern) then
untpd.Apply(
untpd.Block(Nil,
untpd.TypeApply(untpd.ref(defn.Tuple_NamedValue_extract),
untpd.SingletonTypeTree(nameLit) :: Nil)),
arg :: Nil)
else
untpd.Apply(untpd.ref(defn.Tuple_NamedValue_apply), nameLit :: arg :: Nil)
case _ =>
elem

private def isTopLevelDef(stat: Tree)(using Context): Boolean = stat match
case _: ValDef | _: PatDef | _: DefDef | _: Export | _: ExtMethods => true
Expand Down
8 changes: 4 additions & 4 deletions compiler/src/dotty/tools/dotc/ast/untpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -530,15 +530,15 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
def makeSelfDef(name: TermName, tpt: Tree)(using Context): ValDef =
ValDef(name, tpt, EmptyTree).withFlags(PrivateLocal)

def makeTupleOrParens(ts: List[Tree])(using Context): Tree = ts match {
def makeTupleOrParens(ts: List[Tree])(using Context): Tree = ts match
case (t: NamedArg) :: Nil => Tuple(t :: Nil)
case t :: Nil => Parens(t)
case _ => Tuple(ts)
}

def makeTuple(ts: List[Tree])(using Context): Tree = ts match {
def makeTuple(ts: List[Tree])(using Context): Tree = ts match
case (t: NamedArg) :: Nil => Tuple(t :: Nil)
case t :: Nil => t
case _ => Tuple(ts)
}

def makeAndType(left: Tree, right: Tree)(using Context): AppliedTypeTree =
AppliedTypeTree(ref(defn.andType.typeRef), left :: right :: Nil)
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/config/Feature.scala
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ object Feature:
val pureFunctions = experimental("pureFunctions")
val captureChecking = experimental("captureChecking")
val into = experimental("into")
val namedTuples = experimental("namedTuples")

val globalOnlyImports: Set[TermName] = Set(pureFunctions, captureChecking)

Expand Down
19 changes: 19 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import Comments.Comment
import util.Spans.NoSpan
import config.Feature
import Symbols.requiredModuleRef
import Constants.Constant
import cc.{CaptureSet, RetainingType}
import ast.tpd.ref

Expand Down Expand Up @@ -937,6 +938,16 @@ class Definitions {
def TupleClass(using Context): ClassSymbol = TupleTypeRef.symbol.asClass
@tu lazy val Tuple_cons: Symbol = TupleClass.requiredMethod("*:")
@tu lazy val TupleModule: Symbol = requiredModule("scala.Tuple")
@tu lazy val TupleNamedValueModule: Symbol = requiredModule("scala.Tuple.NamedValue")
@tu lazy val Tuple_NamedValue_apply: Symbol = TupleNamedValueModule.requiredMethod("apply")
@tu lazy val Tuple_NamedValue_extract: Symbol = TupleNamedValueModule.requiredMethod("extract")

def Tuple_NamedValueType: TypeRef = TupleModule.termRef.select("NamedValue".toTypeName).asInstanceOf
// Note: It would be dangerous to expose NamedValue as a symbol, since
// NamedValue.typeRef gives the internal view of NamedValue inside Tuple
// which reveals the opaque alias. To see it externally, we need the construction
// above. Without this tweak, named-tuples.scala fails -Ycheck after typer.

@tu lazy val EmptyTupleClass: Symbol = requiredClass("scala.EmptyTuple")
@tu lazy val EmptyTupleModule: Symbol = requiredModule("scala.EmptyTuple")
@tu lazy val NonEmptyTupleTypeRef: TypeRef = requiredClassRef("scala.NonEmptyTuple")
Expand Down Expand Up @@ -1302,6 +1313,14 @@ class Definitions {
case ByNameFunction(_) => true
case _ => false

object NamedTupleElem:
def apply(name: Name, tp: Type)(using Context): Type =
AppliedType(Tuple_NamedValueType, ConstantType(Constant(name.toString)) :: tp :: Nil)
def unapply(t: Type)(using Context): Option[(TermName, Type)] = t match
case AppliedType(tycon, ConstantType(Constant(s: String)) :: tp :: Nil)
if tycon.typeSymbol == Tuple_NamedValueType.typeSymbol => Some((s.toTermName, tp))
case _ => None

final def isCompiletime_S(sym: Symbol)(using Context): Boolean =
sym.name == tpnme.S && sym.owner == CompiletimeOpsIntModuleClass

Expand Down
115 changes: 60 additions & 55 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,14 @@ object Parsers {
ts.toList
else leading :: Nil

def maybeNamed(op: () => Tree): () => Tree = () =>
if isIdent && in.lookahead.token == EQUALS && in.featureEnabled(Feature.namedTuples) then
atSpan(in.offset):
val name = ident()
in.nextToken()
NamedArg(name, op())
else op()

def inSepRegion[T](f: Region => Region)(op: => T): T =
val cur = in.currentRegion
in.currentRegion = f(cur)
Expand Down Expand Up @@ -1528,6 +1536,7 @@ object Parsers {
val start = in.offset
var imods = Modifiers()
var erasedArgs: ListBuffer[Boolean] = ListBuffer()

def functionRest(params: List[Tree]): Tree =
val paramSpan = Span(start, in.lastOffset)
atSpan(start, in.offset) {
Expand Down Expand Up @@ -1574,61 +1583,51 @@ object Parsers {
Function(params, resultType)
}

var isValParamList = false
def convertToElem(t: Tree): Tree = t match
case ByNameTypeTree(t1) =>
syntaxError(ByNameParameterNotSupported(t), t.span)
t1
case ValDef(name, tpt, _) => NamedArg(name, convertToElem(tpt))
case _ => t

val t =
if (in.token == LPAREN) {
if in.token == LPAREN then
in.nextToken()
if (in.token == RPAREN) {
if in.token == RPAREN then
in.nextToken()
functionRest(Nil)
}
else {
else
val paramStart = in.offset
def addErased() =
erasedArgs.addOne(isErasedKw)
if isErasedKw then { in.skipToken(); }
erasedArgs += isErasedKw
if isErasedKw then in.nextToken()
addErased()
val ts = in.currentRegion.withCommasExpected {
var ts = in.currentRegion.withCommasExpected:
funArgType() match
case Ident(name) if name != tpnme.WILDCARD && in.isColon =>
isValParamList = true
def funParam(start: Offset, mods: Modifiers) = {
atSpan(start) {
def funParam(start: Offset, mods: Modifiers) =
atSpan(start):
addErased()
typedFunParam(in.offset, ident(), imods)
}
}
commaSeparatedRest(
typedFunParam(paramStart, name.toTermName, imods),
() => funParam(in.offset, imods))
case t =>
def funParam() = {
addErased()
funArgType()
}
def funParam() =
addErased()
funArgType()
commaSeparatedRest(t, funParam)
}
accept(RPAREN)
if isValParamList || in.isArrow || isPureArrow then
if in.isArrow || isPureArrow || erasedArgs.contains(true) then
functionRest(ts)
else {
val ts1 = ts.mapConserve { t =>
if isByNameType(t) then
syntaxError(ByNameParameterNotSupported(t), t.span)
stripByNameType(t)
else
t
}
val tuple = atSpan(start) { makeTupleOrParens(ts1) }
else
val tuple = atSpan(start):
makeTupleOrParens(ts.mapConserve(convertToElem))
infixTypeRest(
refinedTypeRest(
withTypeRest(
annotTypeRest(
simpleTypeRest(tuple)))))
}
}
}
else if (in.token == LBRACKET) {
val start = in.offset
val tparams = typeParamClause(ParamOwner.TypeParam)
Expand Down Expand Up @@ -1913,6 +1912,7 @@ object Parsers {
* | Singleton `.' id
* | Singleton `.' type
* | ‘(’ ArgTypes ‘)’
* | ‘(’ NamesAndTypes ‘)’
* | Refinement
* | TypeSplice -- deprecated syntax (since 3.0.0)
* | SimpleType1 TypeArgs
Expand All @@ -1921,7 +1921,7 @@ object Parsers {
def simpleType1() = simpleTypeRest {
if in.token == LPAREN then
atSpan(in.offset) {
makeTupleOrParens(inParensWithCommas(argTypes(namedOK = false, wildOK = true)))
makeTupleOrParens(inParensWithCommas(argTypes(namedOK = false, wildOK = true, tupleOK = true)))
}
else if in.token == LBRACE then
atSpan(in.offset) { RefinedTypeTree(EmptyTree, refinement(indentOK = false)) }
Expand Down Expand Up @@ -2004,32 +2004,31 @@ object Parsers {
/** ArgTypes ::= Type {`,' Type}
* | NamedTypeArg {`,' NamedTypeArg}
* NamedTypeArg ::= id `=' Type
* NamesAndTypes ::= NameAndType {‘,’ NameAndType}
* NameAndType ::= id ':' Type
*/
def argTypes(namedOK: Boolean, wildOK: Boolean): List[Tree] = {

def argType() = {
def argTypes(namedOK: Boolean, wildOK: Boolean, tupleOK: Boolean): List[Tree] =
def argType() =
val t = typ()
if (wildOK) t else rejectWildcardType(t)
}
if wildOK then t else rejectWildcardType(t)

def namedTypeArg() = {
def namedArgType() =
val name = ident()
accept(EQUALS)
NamedArg(name.toTypeName, argType())
}

if (namedOK && in.token == IDENTIFIER)
in.currentRegion.withCommasExpected {
argType() match {
case Ident(name) if in.token == EQUALS =>
in.nextToken()
commaSeparatedRest(NamedArg(name, argType()), () => namedTypeArg())
case firstArg =>
commaSeparatedRest(firstArg, () => argType())
}
}
else commaSeparated(() => argType())
}
def namedElem() =
val name = ident()
acceptColon()
NamedArg(name, argType())

if namedOK && isIdent && in.lookahead.token == EQUALS then
commaSeparated(() => namedArgType())
else if tupleOK && isIdent && in.lookahead.isColon && in.featureEnabled(Feature.namedTuples) then
commaSeparated(() => namedElem())
else
commaSeparated(() => argType())
end argTypes

def paramTypeOf(core: () => Tree): Tree =
if in.token == ARROW || isPureArrow(nme.PUREARROW) then
Expand Down Expand Up @@ -2075,7 +2074,7 @@ object Parsers {
* NamedTypeArgs ::= `[' NamedTypeArg {`,' NamedTypeArg} `]'
*/
def typeArgs(namedOK: Boolean, wildOK: Boolean): List[Tree] =
inBracketsWithCommas(argTypes(namedOK, wildOK))
inBracketsWithCommas(argTypes(namedOK, wildOK, tupleOK = false))

/** Refinement ::= `{' RefineStatSeq `}'
*/
Expand Down Expand Up @@ -2651,7 +2650,9 @@ object Parsers {
}

/** ExprsInParens ::= ExprInParens {`,' ExprInParens}
* | NamedExprInParens {‘,’ NamedExprInParens}
* Bindings ::= Binding {`,' Binding}
* NamedExprInParens ::= id '=' ExprInParens
*/
def exprsInParensOrBindings(): List[Tree] =
if in.token == RPAREN then Nil
Expand All @@ -2661,7 +2662,7 @@ object Parsers {
if isErasedKw then isFormalParams = true
if isFormalParams then binding(Modifiers())
else
val t = exprInParens()
val t = maybeNamed(exprInParens)()
if t.isInstanceOf[ValDef] then isFormalParams = true
t
commaSeparatedRest(exprOrBinding(), exprOrBinding)
Expand Down Expand Up @@ -3017,7 +3018,7 @@ object Parsers {
* | Literal
* | Quoted
* | XmlPattern
* | `(' [Patterns] `)'
* | `(' [Patterns | NamedPatterns] `)'
* | SimplePattern1 [TypeArgs] [ArgumentPatterns]
* | ‘given’ RefinedType
* SimplePattern1 ::= SimpleRef
Expand Down Expand Up @@ -3068,9 +3069,13 @@ object Parsers {
p

/** Patterns ::= Pattern [`,' Pattern]
* NamedPatterns ::= NamedPattern {‘,’ NamedPattern}
* NamedPattern ::= id '=' Pattern
*/
def patterns(location: Location = Location.InPattern): List[Tree] =
commaSeparated(() => pattern(location))
val pat = () => pattern(location)
commaSeparated( // TODO: Drop the distinction once we allow named argument patterns
if location == Location.InPattern then maybeNamed(pat) else pat)

def patternsOpt(location: Location = Location.InPattern): List[Tree] =
if (in.token == RPAREN) Nil else patterns(location)
Expand Down
13 changes: 8 additions & 5 deletions compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ class PlainPrinter(_ctx: Context) extends Printer {
homogenize(tp.ref)
case tp @ AppliedType(tycon, args) =>
if (defn.isCompiletimeAppliedType(tycon.typeSymbol)) tp.tryCompiletimeConstantFold
else tycon.dealias.appliedTo(args)
else if !tycon.typeSymbol.isOpaqueAlias then tycon.dealias.appliedTo(args)
else tp
case tp: NamedType =>
tp.reduceProjection
case _ =>
Expand Down Expand Up @@ -120,10 +121,12 @@ class PlainPrinter(_ctx: Context) extends Printer {
}
(keyword ~ refinementNameString(rt) ~ toTextRHS(rt.refinedInfo)).close

protected def argText(arg: Type, isErased: Boolean = false): Text = keywordText("erased ").provided(isErased) ~ (homogenizeArg(arg) match {
case arg: TypeBounds => "?" ~ toText(arg)
case arg => toText(arg)
})
protected def argText(arg: Type, isErased: Boolean = false): Text =
keywordText("erased ").provided(isErased)
~ homogenizeArg(arg).match
case arg: TypeBounds => "?" ~ toText(arg)
case defn.NamedTupleElem(name, arg) => toText(name) ~ ": " ~ argText(arg, isErased)
case arg => toText(arg)

/** Pretty-print comma-separated type arguments for a constructor to be inserted among parentheses or brackets
* (hence with `GlobalPrec` precedence).
Expand Down
7 changes: 5 additions & 2 deletions compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,10 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
def appliedText(tp: Type): Text = tp match
case tp @ AppliedType(tycon, args) =>
tp.tupleElementTypesUpTo(200, normalize = false) match
case Some(types) if types.size >= 2 && !printDebug => toTextTuple(types)
case Some(types @ (defn.NamedTupleElem(_, _) :: _)) if !printDebug =>
toTextTuple(types)
case Some(types) if types.size >= 2 && !printDebug =>
toTextTuple(types)
case _ =>
val tsym = tycon.typeSymbol
if tycon.isRepeatedParam then toTextLocal(args.head) ~ "*"
Expand Down Expand Up @@ -485,7 +488,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
exprText ~ colon ~ toText(tpt)
}
case NamedArg(name, arg) =>
toText(name) ~ " = " ~ toText(arg)
toText(name) ~ (if name.isTermName && arg.isType then " : " else " = ") ~ toText(arg)
case Assign(lhs, rhs) =>
changePrec(GlobalPrec) { toTextLocal(lhs) ~ " = " ~ toText(rhs) }
case block: Block =>
Expand Down
Loading