Skip to content

Inline and implicit matches #5392

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Nov 21, 2018
Merged
Show file tree
Hide file tree
Changes from all 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
6 changes: 3 additions & 3 deletions compiler/src/dotty/tools/dotc/ast/TreeInfo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -399,9 +399,9 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
case Apply(fn, args) =>
def isKnownPureOp(sym: Symbol) =
sym.owner.isPrimitiveValueClass || sym.owner == defn.StringClass
if (tree.tpe.isInstanceOf[ConstantType] && isKnownPureOp(tree.symbol)
// A constant expression with pure arguments is pure.
|| fn.symbol.isStable)
if (tree.tpe.isInstanceOf[ConstantType] && isKnownPureOp(tree.symbol) // A constant expression with pure arguments is pure.
|| fn.symbol.isStable
|| fn.symbol.isPrimaryConstructor && fn.symbol.owner.isNoInitsClass) // TODO: include in isStable?
minOf(exprPurity(fn), args.map(exprPurity)) `min` Pure
else if (fn.symbol.is(Erased)) Pure
else Impure
Expand Down
16 changes: 16 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/Trees.scala
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,12 @@ object Trees {
case class If[-T >: Untyped] private[ast] (cond: Tree[T], thenp: Tree[T], elsep: Tree[T])
extends TermTree[T] {
type ThisTree[-T >: Untyped] = If[T]
def isInline = false
}
class InlineIf[T >: Untyped] private[ast] (cond: Tree[T], thenp: Tree[T], elsep: Tree[T])
extends If(cond, thenp, elsep) {
override def isInline = true
override def toString = s"InlineIf($cond, $thenp, $elsep)"
}

/** A closure with an environment and a reference to a method.
Expand All @@ -515,6 +521,12 @@ object Trees {
case class Match[-T >: Untyped] private[ast] (selector: Tree[T], cases: List[CaseDef[T]])
extends TermTree[T] {
type ThisTree[-T >: Untyped] = Match[T]
def isInline = false
}
class InlineMatch[T >: Untyped] private[ast] (selector: Tree[T], cases: List[CaseDef[T]])
extends Match(selector, cases) {
override def isInline = true
override def toString = s"InlineMatch($selector, $cases)"
}

/** case pat if guard => body; only appears as child of a Match */
Expand Down Expand Up @@ -901,8 +913,10 @@ object Trees {
type Assign = Trees.Assign[T]
type Block = Trees.Block[T]
type If = Trees.If[T]
type InlineIf = Trees.InlineIf[T]
type Closure = Trees.Closure[T]
type Match = Trees.Match[T]
type InlineMatch = Trees.InlineMatch[T]
type CaseDef = Trees.CaseDef[T]
type Labeled = Trees.Labeled[T]
type Return = Trees.Return[T]
Expand Down Expand Up @@ -1030,6 +1044,7 @@ object Trees {
}
def If(tree: Tree)(cond: Tree, thenp: Tree, elsep: Tree)(implicit ctx: Context): If = tree match {
case tree: If if (cond eq tree.cond) && (thenp eq tree.thenp) && (elsep eq tree.elsep) => tree
case tree: InlineIf => finalize(tree, untpd.InlineIf(cond, thenp, elsep))
case _ => finalize(tree, untpd.If(cond, thenp, elsep))
}
def Closure(tree: Tree)(env: List[Tree], meth: Tree, tpt: Tree)(implicit ctx: Context): Closure = tree match {
Expand All @@ -1038,6 +1053,7 @@ object Trees {
}
def Match(tree: Tree)(selector: Tree, cases: List[CaseDef])(implicit ctx: Context): Match = tree match {
case tree: Match if (selector eq tree.selector) && (cases eq tree.cases) => tree
case tree: InlineMatch => finalize(tree, untpd.InlineMatch(selector, cases))
case _ => finalize(tree, untpd.Match(selector, cases))
}
def CaseDef(tree: Tree)(pat: Tree, guard: Tree, body: Tree)(implicit ctx: Context): CaseDef = tree match {
Expand Down
6 changes: 6 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/tpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
def If(cond: Tree, thenp: Tree, elsep: Tree)(implicit ctx: Context): If =
ta.assignType(untpd.If(cond, thenp, elsep), thenp, elsep)

def InlineIf(cond: Tree, thenp: Tree, elsep: Tree)(implicit ctx: Context): If =
ta.assignType(untpd.InlineIf(cond, thenp, elsep), thenp, elsep)

def Closure(env: List[Tree], meth: Tree, tpt: Tree)(implicit ctx: Context): Closure =
ta.assignType(untpd.Closure(env, meth, tpt), meth, tpt)

Expand Down Expand Up @@ -121,6 +124,9 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
def Match(selector: Tree, cases: List[CaseDef])(implicit ctx: Context): Match =
ta.assignType(untpd.Match(selector, cases), selector, cases)

def InlineMatch(selector: Tree, cases: List[CaseDef])(implicit ctx: Context): Match =
ta.assignType(untpd.InlineMatch(selector, cases), selector, cases)

def Labeled(bind: Bind, expr: Tree)(implicit ctx: Context): Labeled =
ta.assignType(untpd.Labeled(bind, expr))

Expand Down
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/untpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -271,8 +271,10 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
def Assign(lhs: Tree, rhs: Tree): Assign = new Assign(lhs, rhs)
def Block(stats: List[Tree], expr: Tree): Block = new Block(stats, expr)
def If(cond: Tree, thenp: Tree, elsep: Tree): If = new If(cond, thenp, elsep)
def InlineIf(cond: Tree, thenp: Tree, elsep: Tree): If = new InlineIf(cond, thenp, elsep)
def Closure(env: List[Tree], meth: Tree, tpt: Tree): Closure = new Closure(env, meth, tpt)
def Match(selector: Tree, cases: List[CaseDef]): Match = new Match(selector, cases)
def InlineMatch(selector: Tree, cases: List[CaseDef]): Match = new InlineMatch(selector, cases)
def CaseDef(pat: Tree, guard: Tree, body: Tree): CaseDef = new CaseDef(pat, guard, body)
def Labeled(bind: Bind, expr: Tree): Labeled = new Labeled(bind, expr)
def Return(expr: Tree, from: Tree): Return = new Return(expr, from)
Expand Down
6 changes: 5 additions & 1 deletion compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,11 @@ class Definitions {
def NullType: TypeRef = NullClass.typeRef
lazy val RuntimeNullModuleRef: TermRef = ctx.requiredModuleRef("scala.runtime.Null")

lazy val ImplicitScrutineeTypeSym =
newSymbol(ScalaPackageClass, tpnme.IMPLICITkw, EmptyFlags, TypeBounds.empty).entered
def ImplicitScrutineeTypeRef: TypeRef = ImplicitScrutineeTypeSym.typeRef


lazy val ScalaPredefModuleRef: TermRef = ctx.requiredModuleRef("scala.Predef")
def ScalaPredefModule(implicit ctx: Context): Symbol = ScalaPredefModuleRef.symbol

Expand Down Expand Up @@ -1296,5 +1301,4 @@ class Definitions {
isInitialized = true
}
}

}
5 changes: 4 additions & 1 deletion compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -793,7 +793,10 @@ object SymDenotations {
def isSkolem: Boolean = name == nme.SKOLEM

def isInlineMethod(implicit ctx: Context): Boolean =
is(InlineMethod, butNot = AccessorOrSynthetic)
is(InlineMethod, butNot = AccessorOrSynthetic) &&
name != nme.unapply // unapply methods do not count as inline methods
// we need an inline flag on them only do that
// reduceProjection gets access to their rhs

/** An erased value or an inline method, excluding @forceInline annotated methods.
* The latter have to be kept around to get to parity with Scala.
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ Standard-Section: "ASTs" TopLevelStat*
BLOCK Length expr_Term Stat*
INLINED Length expr_Term call_Term? ValOrDefDef*
LAMBDA Length meth_Term target_Type?
IF Length cond_Term then_Term else_Term
MATCH Length sel_Term CaseDef*
IF Length [INLINE] cond_Term then_Term else_Term
MATCH Length (IMPLICIT | [INLINE] sel_Term) CaseDef*
TRY Length expr_Term CaseDef* finalizer_Term?
RETURN Length meth_ASTRef expr_Term?
WHILE Length cond_Term body_Term
Expand Down
20 changes: 16 additions & 4 deletions compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -414,19 +414,31 @@ class TreePickler(pickler: TastyPickler) {
writeByte(BLOCK)
stats.foreach(preRegister)
withLength { pickleTree(expr); stats.foreach(pickleTree) }
case If(cond, thenp, elsep) =>
case tree @ If(cond, thenp, elsep) =>
writeByte(IF)
withLength { pickleTree(cond); pickleTree(thenp); pickleTree(elsep) }
withLength {
if (tree.isInline) writeByte(INLINE)
pickleTree(cond)
pickleTree(thenp)
pickleTree(elsep)
}
case Closure(env, meth, tpt) =>
writeByte(LAMBDA)
assert(env.isEmpty)
withLength {
pickleTree(meth)
if (tpt.tpe.exists) pickleTpt(tpt)
}
case Match(selector, cases) =>
case tree @ Match(selector, cases) =>
writeByte(MATCH)
withLength { pickleTree(selector); cases.foreach(pickleTree) }
withLength {
if (tree.isInline) {
if (selector.isEmpty) writeByte(IMPLICIT)
else { writeByte(INLINE); pickleTree(selector) }
}
else pickleTree(selector)
tree.cases.foreach(pickleTree)
}
case CaseDef(pat, guard, rhs) =>
writeByte(CASEDEF)
withLength { pickleTree(pat); pickleTree(rhs); pickleTreeUnlessEmpty(guard) }
Expand Down
17 changes: 15 additions & 2 deletions compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1081,13 +1081,26 @@ class TreeUnpickler(reader: TastyReader,
val expansion = exprReader.readTerm() // need bindings in scope, so needs to be read before
Inlined(call, bindings, expansion)
case IF =>
If(readTerm(), readTerm(), readTerm())
if (nextByte == INLINE) {
readByte()
InlineIf(readTerm(), readTerm(), readTerm())
}
else
If(readTerm(), readTerm(), readTerm())
case LAMBDA =>
val meth = readTerm()
val tpt = ifBefore(end)(readTpt(), EmptyTree)
Closure(Nil, meth, tpt)
case MATCH =>
Match(readTerm(), readCases(end))
if (nextByte == IMPLICIT) {
readByte()
InlineMatch(EmptyTree, readCases(end))
}
else if (nextByte == INLINE) {
readByte()
InlineMatch(readTerm(), readCases(end))
}
else Match(readTerm(), readCases(end))
case RETURN =>
val from = readSymRef()
val expr = ifBefore(end)(readTerm(), EmptyTree)
Expand Down
53 changes: 47 additions & 6 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import StdNames._
import util.Positions._
import Constants._
import ScriptParsers._
import Decorators._
import scala.annotation.{tailrec, switch}
import rewrites.Rewrites.patch

Expand Down Expand Up @@ -1127,7 +1128,8 @@ object Parsers {
val start = in.offset
if (in.token == IMPLICIT || in.token == ERASED) {
val imods = modifiers(funArgMods)
implicitClosure(start, location, imods)
if (in.token == MATCH) implicitMatch(start, imods)
else implicitClosure(start, location, imods)
} else {
val saved = placeholderParams
placeholderParams = Nil
Expand Down Expand Up @@ -1207,7 +1209,21 @@ object Parsers {
case FOR =>
forExpr()
case _ =>
expr1Rest(postfixExpr(), location)
if (isIdent(nme.inline) && !in.inModifierPosition() && in.lookaheadIn(canStartExpressionTokens)) {
val start = in.skipToken()
in.token match {
case IF =>
ifExpr(start, InlineIf)
case _ =>
val t = postfixExpr()
if (in.token == MATCH) matchExpr(t, start, InlineMatch)
else {
syntaxErrorOrIncomplete(i"`match` or `if` expected but ${in.token} found")
t
}
}
}
else expr1Rest(postfixExpr(), location)
}

def expr1Rest(t: Tree, location: Location.Value): Tree = in.token match {
Expand All @@ -1221,7 +1237,7 @@ object Parsers {
case COLON =>
ascription(t, location)
case MATCH =>
matchExpr(t, startOffset(t))
matchExpr(t, startOffset(t), Match)
case _ =>
t
}
Expand Down Expand Up @@ -1266,13 +1282,36 @@ object Parsers {
}

/** `match' { CaseClauses }
* `match' { ImplicitCaseClauses }
*/
def matchExpr(t: Tree, start: Offset): Match =
def matchExpr(t: Tree, start: Offset, mkMatch: (Tree, List[CaseDef]) => Match) =
atPos(start, in.skipToken()) {
inBraces(Match(t, caseClauses(caseClause)))
inBraces(mkMatch(t, caseClauses(caseClause)))
}

/** `match' { ImplicitCaseClauses }
*/
def implicitMatch(start: Int, imods: Modifiers) = {
def markFirstIllegal(mods: List[Mod]) = mods match {
case mod :: _ => syntaxError(em"illegal modifier for implicit match", mod.pos)
case _ =>
}
imods.mods match {
case Mod.Implicit() :: mods => markFirstIllegal(mods)
case mods => markFirstIllegal(mods)
}
val result @ Match(t, cases) =
matchExpr(EmptyTree, start, InlineMatch)
for (CaseDef(pat, _, _) <- cases) {
def isImplicitPattern(pat: Tree) = pat match {
case Typed(pat1, _) => isVarPattern(pat1)
case pat => isVarPattern(pat)
}
if (!isImplicitPattern(pat))
syntaxError(em"not a legal pattern for an implicit match", pat.pos)
}
result
}

/** `match' { TypeCaseClauses }
*/
def matchType(bound: Tree, t: Tree): MatchTypeTree =
Expand Down Expand Up @@ -2620,6 +2659,8 @@ object Parsers {
var imods = modifiers(funArgMods)
if (isBindingIntro)
stats += implicitClosure(start, Location.InBlock, imods)
else if (in.token == MATCH)
stats += implicitMatch(start, imods)
else
stats +++= localDef(start, imods)
} else {
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/parsing/Tokens.scala
Original file line number Diff line number Diff line change
Expand Up @@ -249,5 +249,5 @@ object Tokens extends TokensCommon {

final val numericLitTokens: TokenSet = BitSet(INTLIT, LONGLIT, FLOATLIT, DOUBLELIT)

final val softModifierNames = Set(nme.`inline`, nme.`opaque`)
final val softModifierNames = Set(nme.inline, nme.opaque)
}
17 changes: 14 additions & 3 deletions compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -361,15 +361,26 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
case block: Block =>
blockToText(block)
case If(cond, thenp, elsep) =>
val isInline = tree.isInstanceOf[Trees.InlineIf[_]]
changePrec(GlobalPrec) {
keywordStr("if ") ~ toText(cond) ~ (keywordText(" then") provided !cond.isInstanceOf[Parens]) ~~ toText(thenp) ~ optText(elsep)(keywordStr(" else ") ~ _)
keywordStr(if (isInline) "inline if " else "if ") ~
toText(cond) ~ (keywordText(" then") provided !cond.isInstanceOf[Parens]) ~~
toText(thenp) ~ optText(elsep)(keywordStr(" else ") ~ _)
}
case Closure(env, ref, target) =>
"closure(" ~ (toTextGlobal(env, ", ") ~ " | " provided env.nonEmpty) ~
toTextGlobal(ref) ~ (":" ~ toText(target) provided !target.isEmpty) ~ ")"
case Match(sel, cases) =>
if (sel.isEmpty) blockText(cases)
else changePrec(GlobalPrec) { toText(sel) ~ keywordStr(" match ") ~ blockText(cases) }
val isInline = tree.isInstanceOf[Trees.InlineMatch[_]]
if (sel.isEmpty && !isInline) blockText(cases)
else changePrec(GlobalPrec) {
val selTxt: Text =
if (isInline)
if (sel.isEmpty) keywordStr("implicit")
else keywordStr("inline ") ~ toText(sel)
else toText(sel)
selTxt ~ keywordStr(" match ") ~ blockText(cases)
}
case CaseDef(pat, guard, body) =>
keywordStr("case ") ~ inPattern(toText(pat)) ~ optText(guard)(keywordStr(" if ") ~ _) ~ " => " ~ caseBlockText(body)
case Labeled(bind, expr) =>
Expand Down
18 changes: 10 additions & 8 deletions compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,18 @@ class PatternMatcher extends MiniPhase {
override def phaseName: String = PatternMatcher.name
override def runsAfter: Set[String] = Set(ElimRepeated.name)

override def transformMatch(tree: Match)(implicit ctx: Context): Tree = {
val translated = new Translator(tree.tpe, this).translateMatch(tree)
override def transformMatch(tree: Match)(implicit ctx: Context): Tree =
if (tree.isInstanceOf[InlineMatch]) tree
else {
val translated = new Translator(tree.tpe, this).translateMatch(tree)

// check exhaustivity and unreachability
val engine = new patmat.SpaceEngine
engine.checkExhaustivity(tree)
engine.checkRedundancy(tree)
// check exhaustivity and unreachability
val engine = new patmat.SpaceEngine
engine.checkExhaustivity(tree)
engine.checkRedundancy(tree)

translated.ensureConforms(tree.tpe)
}
translated.ensureConforms(tree.tpe)
}
}

object PatternMatcher {
Expand Down
5 changes: 1 addition & 4 deletions compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ object Applications {
def productSelectorTypes(tp: Type, errorPos: Position = NoPosition)(implicit ctx: Context): List[Type] = {
def tupleSelectors(n: Int, tp: Type): List[Type] = {
val sel = extractorMemberType(tp, nme.selectorName(n), errorPos)
// extractorMemberType will return NoType if this is the tail of tuple with an unknown tail
// extractorMemberType will return NoType if this is the tail of tuple with an unknown tail
// such as `Int *: T` where `T <: Tuple`.
if (sel.exists) sel :: tupleSelectors(n + 1, tp) else Nil
}
Expand Down Expand Up @@ -982,9 +982,6 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
else trySelectUnapply(qual1)(_ => notAnExtractor(sel))
}

if (unapplyFn.symbol.isInlineMethod)
checkInInlineContext("implementation restriction: call to inline unapply", tree.pos)

/** Add a `Bind` node for each `bound` symbol in a type application `unapp` */
def addBinders(unapp: Tree, bound: List[Symbol]) = unapp match {
case TypeApply(fn, args) =>
Expand Down
Loading