diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index 30eb2b16ba5e..1995822395d7 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -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 diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index 0e83edb8e49e..69d8b32bd36e 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -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. @@ -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 */ @@ -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] @@ -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 { @@ -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 { diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index eb0ad1dc3384..8251f6ac0256 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -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) @@ -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)) diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index cfdd2af56df2..6d23884b0d32 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -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) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index ce1103752523..37d5ac8ee4cc 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -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 @@ -1296,5 +1301,4 @@ class Definitions { isInitialized = true } } - } diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index d90ef5134976..4b6261901d5f 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -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. diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala index 0bb3a26b78af..0a29061a8a1f 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala @@ -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 diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 2960c4ebc3f7..459df41b18b9 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -414,9 +414,14 @@ 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) @@ -424,9 +429,16 @@ class TreePickler(pickler: TastyPickler) { 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) } diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index aa8594b6506d..2f93be0b1687 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -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) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 79bc1d46b5dd..70541b54857b 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -20,6 +20,7 @@ import StdNames._ import util.Positions._ import Constants._ import ScriptParsers._ +import Decorators._ import scala.annotation.{tailrec, switch} import rewrites.Rewrites.patch @@ -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 @@ -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 { @@ -1221,7 +1237,7 @@ object Parsers { case COLON => ascription(t, location) case MATCH => - matchExpr(t, startOffset(t)) + matchExpr(t, startOffset(t), Match) case _ => t } @@ -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 = @@ -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 { diff --git a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala index 1d1ded5e13b9..b9afcf03aa71 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala @@ -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) } diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index fdbf6963f18b..d19834955908 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -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) => diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index c2a69e409a03..8bac04c0e509 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -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 { diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 5d62dcdbed55..d2663fc5ef17 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -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 } @@ -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) => diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 3e883b64f61a..7d9d21e5ffb2 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -891,8 +891,14 @@ trait Checking { /** Check that we are in an inline context (inside an inline method or in inline code) */ def checkInInlineContext(what: String, pos: Position)(implicit ctx: Context): Unit = - if (!ctx.inInlineMethod && !ctx.isInlineContext) - ctx.error(em"$what can only be used in an inline method", pos) + if (!ctx.inInlineMethod && !ctx.isInlineContext) { + val inInlineUnapply = ctx.owner.ownersIterator.exists(owner => + owner.name == nme.unapply && owner.is(Inline) && owner.is(Method)) + val msg = + if (inInlineUnapply) "cannot be used in an inline unapply" + else "can only be used in an inline method" + ctx.error(em"$what $msg", pos) + } /** Check that all case classes that extend `scala.Enum` are `enum` cases */ def checkEnum(cdef: untpd.TypeDef, cls: Symbol, parent: Symbol)(implicit ctx: Context): Unit = { diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index ef61b24a6fb5..5b1c9fe47ec4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -11,8 +11,10 @@ import Types._ import Decorators._ import Constants._ import StdNames._ +import transform.SymUtils._ import Contexts.Context -import Names.Name +import Names.{Name, TermName} +import NameKinds.{InlineAccessorName, InlineScrutineeName, InlineBinderName} import ProtoTypes.selectionProto import SymDenotations.SymDenotation import Inferencing.fullyDefinedType @@ -21,6 +23,7 @@ import ErrorReporting.errorTree import collection.mutable import reporting.trace import util.Positions.Position +import ast.TreeInfo object Inliner { import tpd._ @@ -155,6 +158,7 @@ object Inliner { */ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { import tpd._ + import Inliner._ private val (methPart, callTypeArgs, callValueArgss) = decomposeCall(call) private val inlinedMethod = methPart.symbol @@ -192,7 +196,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { private val thisProxy = new mutable.HashMap[ClassSymbol, TermRef] /** A buffer for bindings that define proxies for actual arguments */ - private val bindingsBuf = new mutable.ListBuffer[ValOrDefDef] + private val bindingsBuf = new mutable.ListBuffer[MemberDef] private def newSym(name: Name, flags: FlagSet, info: Type): Symbol = ctx.newSymbol(ctx.owner, name, flags, info, coord = call.pos) @@ -207,7 +211,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { * @param bindingsBuf the buffer to which the definition should be appended */ private def paramBindingDef(name: Name, paramtp: Type, arg: Tree, - bindingsBuf: mutable.ListBuffer[ValOrDefDef]): ValOrDefDef = { + bindingsBuf: mutable.ListBuffer[MemberDef]): MemberDef = { val argtpe = arg.tpe.dealiasKeepAnnots val isByName = paramtp.dealias.isInstanceOf[ExprType] var inlineFlag = InlineProxy @@ -444,6 +448,9 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { trace(i"inlining $call", inlining, show = true) { + // The normalized bindings collected in `bindingsBuf` + bindingsBuf.transform(reducer.normalizeBinding(_)(inlineCtx)) + // Run a typing pass over the inlined tree. See InlineTyper for details. val expansion1 = inlineTyper.typed(expansion, pt)(inlineCtx) @@ -454,19 +461,135 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { } // Drop unused bindings - val (finalBindings, finalExpansion) = dropUnusedDefs(bindingsBuf.toList, expansion1) + val matchBindings = reducer.matchBindingsBuf.toList + val (finalBindings, finalExpansion) = dropUnusedDefs(bindingsBuf.toList ++ matchBindings, expansion1) + val (finalMatchBindings, finalArgBindings) = finalBindings.partition(matchBindings.contains(_)) if (inlinedMethod == defn.Typelevel_error) issueError() // Take care that only argument bindings go into `bindings`, since positions are // different for bindings from arguments and bindings from body. - tpd.Inlined(call, finalBindings, finalExpansion) + tpd.Inlined(call, finalArgBindings, seq(finalMatchBindings, finalExpansion)) } } /** A utility object offering methods for rewriting inlined code */ object reducer { + /** Additional bindings established by reducing match expressions */ + val matchBindingsBuf = new mutable.ListBuffer[MemberDef] + + /** An extractor for terms equivalent to `new C(args)`, returning the class `C`, + * a list of bindings, and the arguments `args`. Can see inside blocks and Inlined nodes and can + * follow a reference to an inline value binding to its right hand side. + * @return optionally, a triple consisting of + * - the class `C` + * - the arguments `args` + * - any bindings that wrap the instance creation + * - whether the instance creation is precomputed or by-name + */ + private object NewInstance { + def unapply(tree: Tree)(implicit ctx: Context): Option[(Symbol, List[Tree], List[Tree], Boolean)] = { + def unapplyLet(bindings: List[Tree], expr: Tree) = + unapply(expr) map { + case (cls, reduced, prefix, precomputed) => (cls, reduced, bindings ::: prefix, precomputed) + } + tree match { + case Apply(fn, args) => + fn match { + case Select(New(tpt), nme.CONSTRUCTOR) => + Some((tpt.tpe.classSymbol, args, Nil, false)) + case TypeApply(Select(New(tpt), nme.CONSTRUCTOR), _) => + Some((tpt.tpe.classSymbol, args, Nil, false)) + case _ => + val meth = fn.symbol + if (meth.name == nme.apply && + meth.flags.is(Synthetic) && + meth.owner.linkedClass.is(Case)) + Some(meth.owner.linkedClass, args, Nil, false) + else None + } + case Ident(_) => + val binding = tree.symbol.defTree + for ((cls, reduced, prefix, precomputed) <- unapply(binding)) + yield (cls, reduced, prefix, precomputed || binding.isInstanceOf[ValDef]) + case Inlined(_, bindings, expansion) => + unapplyLet(bindings, expansion) + case Block(stats, expr) if isPureExpr(tree) => + unapplyLet(stats, expr) + case _ => + None + } + } + } + + /** If `tree` is equivalent to `new C(args).x` where class `C` does not have + * initialization code and `x` is a parameter corresponding to one of the + * arguments `args`, the corresponding argument, otherwise `tree` itself. + * Side effects of original arguments need to be preserved. + */ + def reduceProjection(tree: Tree)(implicit ctx: Context): Tree = { + if (ctx.debug) inlining.println(i"try reduce projection $tree") + tree match { + case Select(NewInstance(cls, args, prefix, precomputed), field) if cls.isNoInitsClass => + def matches(param: Symbol, selection: Symbol): Boolean = + param == selection || { + selection.name match { + case InlineAccessorName(underlying) => + param.name == underlying && selection.info.isInstanceOf[ExprType] + case _ => + false + } + } + val idx = cls.asClass.paramAccessors.indexWhere(matches(_, tree.symbol)) + if (idx >= 0 && idx < args.length) { + def finish(arg: Tree) = + new TreeTypeMap().transform(arg) // make sure local bindings in argument have fresh symbols + .reporting(res => i"projecting $tree -> $res", inlining) + val arg = args(idx) + if (precomputed) + if (isPureExpr(arg)) finish(arg) + else tree // nothing we can do here, projection would duplicate side effect + else { + // newInstance is evaluated in place, need to reflect side effects of + // arguments in the order they were written originally + def collectImpure(from: Int, end: Int) = + (from until end).filterNot(i => isPureExpr(args(i))).toList.map(args) + val leading = collectImpure(0, idx) + val trailing = collectImpure(idx + 1, args.length) + val argInPlace = + if (trailing.isEmpty) arg + else letBindUnless(TreeInfo.Pure, arg)(seq(trailing, _)) + finish(seq(prefix, seq(leading, argInPlace))) + } + } + else tree + case Block(stats, expr) if stats.forall(isPureBinding) => + cpy.Block(tree)(stats, reduceProjection(expr)) + case _ => tree + } + } + + /** If this is a value binding: + * - reduce its rhs if it is a projection and adjust its type accordingly, + * - record symbol -> rhs in the InlineBindings context propery. + */ + def normalizeBinding(binding: MemberDef)(implicit ctx: Context) = { + val binding1 = binding match { + case binding: ValDef => + val rhs1 = reduceProjection(binding.rhs) + binding.symbol.defTree = rhs1 + if (rhs1 `eq` binding.rhs) binding + else { + binding.symbol.info = rhs1.tpe + cpy.ValDef(binding)(tpt = TypeTree(rhs1.tpe), rhs = rhs1) + } + case _ => + binding + } + binding1.withPos(call.pos) + } + /** An extractor for references to inlineable arguments. These are : * - by-value arguments marked with `inline` * - all by-name arguments @@ -474,9 +597,9 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { private object InlineableArg { lazy val paramProxies = paramProxy.values.toSet def unapply(tree: Trees.Ident[_])(implicit ctx: Context): Option[Tree] = { - def search(buf: mutable.ListBuffer[ValOrDefDef]) = buf.find(_.name == tree.name) + def search(buf: mutable.ListBuffer[MemberDef]) = buf.find(_.name == tree.name) if (paramProxies.contains(tree.typeOpt)) - search(bindingsBuf) match { + search(bindingsBuf).orElse(search(matchBindingsBuf)) match { case Some(vdef: ValDef) if vdef.symbol.is(Inline) => Some(integrate(vdef.rhs, vdef.symbol)) case Some(ddef: DefDef) => @@ -517,7 +640,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { case Apply(Select(cl @ closureDef(ddef), nme.apply), args) if defn.isFunctionType(cl.tpe) => ddef.tpe.widen match { case mt: MethodType if ddef.vparamss.head.length == args.length => - val bindingsBuf = new mutable.ListBuffer[ValOrDefDef] + val bindingsBuf = new mutable.ListBuffer[MemberDef] val argSyms = (mt.paramNames, mt.paramInfos, args).zipped.map { (name, paramtp, arg) => arg.tpe.dealias match { case ref @ TermRef(NoPrefix, _) => ref.symbol @@ -534,6 +657,180 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { } case _ => tree } + + /** The result type of reducing a match. It consists optionally of a list of bindings + * for the pattern-bound variables and the RHS of the selected case. + * Returns `None` if no case was selected. + */ + type MatchRedux = Option[(List[MemberDef], untpd.Tree)] + + /** Reduce an inline match + * @param mtch the match tree + * @param scrutinee the scrutinee expression, assumed to be pure, or + * EmptyTree for an implicit match + * @param scrutType its fully defined type, or + * ImplicitScrutineeTypeRef for an implicit match + * @param typer The current inline typer + * @return optionally, if match can be reduced to a matching case: A pair of + * bindings for all pattern-bound variables and the RHS of the case. + */ + def reduceInlineMatch(scrutinee: Tree, scrutType: Type, cases: List[untpd.CaseDef], typer: Typer)(implicit ctx: Context): MatchRedux = { + + val isImplicit = scrutinee.isEmpty + val gadtSyms = typer.gadtSyms(scrutType) + + /** Try to match pattern `pat` against scrutinee reference `scrut`. If successful add + * bindings for variables bound in this pattern to `bindingsBuf`. + */ + def reducePattern(bindingsBuf: mutable.ListBuffer[MemberDef], scrut: TermRef, pat: Tree)(implicit ctx: Context): Boolean = { + + /** Create a binding of a pattern bound variable with matching part of + * scrutinee as RHS and type that corresponds to RHS. + */ + def newBinding(sym: TermSymbol, rhs: Tree): Unit = { + sym.info = rhs.tpe.widenTermRefExpr + bindingsBuf += ValDef(sym, constToLiteral(rhs)) + } + + def searchImplicit(sym: TermSymbol, tpt: Tree) = { + val evTyper = new Typer + val evidence = evTyper.inferImplicitArg(tpt.tpe, tpt.pos)(ctx.fresh.setTyper(evTyper)) + evidence.tpe match { + case fail: Implicits.AmbiguousImplicits => + ctx.error(evTyper.missingArgMsg(evidence, tpt.tpe, ""), tpt.pos) + true // hard error: return true to stop implicit search here + case fail: Implicits.SearchFailureType => + false + case _ => + newBinding(sym, evidence) + true + } + } + + pat match { + case Typed(pat1, tpt) => + val getBoundVars = new TreeAccumulator[List[TypeSymbol]] { + def apply(syms: List[TypeSymbol], t: Tree)(implicit ctx: Context) = { + val syms1 = t match { + case t: Bind if t.symbol.isType && t.name != tpnme.WILDCARD => + t.symbol.asType :: syms + case _ => + syms + } + foldOver(syms1, t) + } + } + var boundVars = getBoundVars(Nil, tpt) + // UnApply nodes with pattern bound variables translate to something like this + // UnApply[t @ t](pats)(implicits): T[t] + // Need to traverse any binds in type arguments of the UnAppyl to get the set of + // all instantiable type variables. Test case is pos/inline-caseclass.scala. + pat1 match { + case UnApply(TypeApply(_, tpts), _, _) => + for (tpt <- tpts) boundVars = getBoundVars(boundVars, tpt) + case _ => + } + for (bv <- boundVars) ctx.gadt.setBounds(bv, bv.info.bounds) + scrut <:< tpt.tpe && { + for (bv <- boundVars) { + bv.info = TypeAlias(ctx.gadt.bounds(bv).lo) + // FIXME: This is very crude. We should approximate with lower or higher bound depending + // on variance, and we should also take care of recursive bounds. Basically what + // ConstraintHandler#approximation does. However, this only works for constrained paramrefs + // not GADT-bound variables. Hopefully we will get some way to improve this when we + // re-implement GADTs in terms of constraints. + bindingsBuf += TypeDef(bv) + } + reducePattern(bindingsBuf, scrut, pat1) + } + case pat @ Bind(name: TermName, Typed(_, tpt)) if isImplicit => + searchImplicit(pat.symbol.asTerm, tpt) + case pat @ Bind(name: TermName, body) => + reducePattern(bindingsBuf, scrut, body) && { + if (name != nme.WILDCARD) newBinding(pat.symbol.asTerm, ref(scrut)) + true + } + case Ident(nme.WILDCARD) => + true + case pat: Literal => + scrut.widenTermRefExpr =:= pat.tpe + case pat: RefTree => + scrut =:= pat.tpe || + scrut.widen.classSymbol.is(Module) && scrut.widen =:= pat.tpe.widen && { + scrut.prefix match { + case _: SingletonType | NoPrefix => true + case _ => false + } + } + case UnApply(unapp, _, pats) => + unapp.tpe.widen match { + case mt: MethodType if mt.paramInfos.length == 1 => + + def reduceSubPatterns(pats: List[Tree], selectors: List[Tree]): Boolean = (pats, selectors) match { + case (Nil, Nil) => true + case (pat :: pats1, selector :: selectors1) => + val elem = newSym(InlineBinderName.fresh(), Synthetic, selector.tpe.widenTermRefExpr).asTerm + newBinding(elem, selector) + reducePattern(bindingsBuf, elem.termRef, pat) && + reduceSubPatterns(pats1, selectors1) + case _ => false + } + + val paramType = mt.paramInfos.head + val paramCls = paramType.classSymbol + if (paramCls.is(Case) && unapp.symbol.is(Synthetic) && scrut <:< paramType) { + val caseAccessors = + if (paramCls.is(Scala2x)) paramCls.caseAccessors.filter(_.is(Method)) + else paramCls.asClass.paramAccessors + val selectors = + for (accessor <- caseAccessors) + yield constToLiteral(reduceProjection(ref(scrut).select(accessor).ensureApplied)) + caseAccessors.length == pats.length && reduceSubPatterns(pats, selectors) + } + else if (unapp.symbol.isInlineMethod) { // TODO: Adapt to typed setting + val app = untpd.Apply(untpd.TypedSplice(unapp), untpd.ref(scrut)) + val app1 = typer.typedExpr(app) + val args = tupleArgs(app1) + args.nonEmpty && reduceSubPatterns(pats, args) + } + else false + case _ => + false + } + case _ => false + } + } + + /** The initial scrutinee binding: `val $scrutineeN = ` */ + val scrutineeSym = newSym(InlineScrutineeName.fresh(), Synthetic, scrutType).asTerm + val scrutineeBinding = normalizeBinding(ValDef(scrutineeSym, scrutinee)) + + def reduceCase(cdef: untpd.CaseDef): MatchRedux = { + val caseBindingsBuf = new mutable.ListBuffer[MemberDef]() + def guardOK(implicit ctx: Context) = cdef.guard.isEmpty || { + val guardCtx = ctx.fresh.setNewScope + caseBindingsBuf.foreach(binding => guardCtx.enter(binding.symbol)) + typer.typed(cdef.guard, defn.BooleanType)(guardCtx) match { + case ConstantValue(true) => true + case _ => false + } + } + if (!isImplicit) caseBindingsBuf += scrutineeBinding + val gadtCtx = typer.gadtContext(gadtSyms).addMode(Mode.GADTflexible) + val pat1 = typer.typedPattern(cdef.pat, scrutType)(gadtCtx) + if (reducePattern(caseBindingsBuf, scrutineeSym.termRef, pat1)(gadtCtx) && guardOK) + Some((caseBindingsBuf.toList, cdef.body)) + else + None + } + + def recur(cases: List[untpd.CaseDef]): MatchRedux = cases match { + case Nil => None + case cdef :: cases1 => reduceCase(cdef) `orElse` recur(cases1) + } + + recur(cases) + } } /** A typer for inlined bodies. Beyond standard typing, an inline typer performs @@ -561,7 +858,6 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { super.ensureAccessible(tpe, superAccess, pos) } - override def typedIdent(tree: untpd.Ident, pt: Type)(implicit ctx: Context): Tree = tryInline(tree.asInstanceOf[tpd.Tree]) `orElse` super.typedIdent(tree, pt) @@ -581,25 +877,60 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { if (isIdempotentExpr(cond1)) selected else Block(cond1 :: Nil, selected) case cond1 => - val if1 = untpd.cpy.If(tree)(cond = untpd.TypedSplice(cond1)) - super.typedIf(if1, pt) + if (tree.isInline) + errorTree(tree, em"""cannot reduce inline if + | its condition ${tree.cond} + | is not a constant value""") + else { + val if1 = untpd.cpy.If(tree)(cond = untpd.TypedSplice(cond1)) + super.typedIf(if1, pt) + } } override def typedApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context): Tree = constToLiteral(betaReduce(super.typedApply(tree, pt))) + override def typedMatchFinish(tree: untpd.Match, sel: Tree, wideSelType: Type, cases: List[untpd.CaseDef], pt: Type)(implicit ctx: Context) = + if (!tree.isInline || ctx.owner.isInlineMethod) // don't reduce match of nested inline method yet + super.typedMatchFinish(tree, sel, wideSelType, cases, pt) + else { + val selType = if (sel.isEmpty) wideSelType else sel.tpe + reduceInlineMatch(sel, selType, cases, this) match { + case Some((caseBindings, rhs)) => + var rhsCtx = ctx.fresh.setNewScope + for (binding <- caseBindings) { + matchBindingsBuf += binding + rhsCtx.enter(binding.symbol) + } + typedExpr(rhs, pt)(rhsCtx) + case None => + def guardStr(guard: untpd.Tree) = if (guard.isEmpty) "" else i" if $guard" + def patStr(cdef: untpd.CaseDef) = i"case ${cdef.pat}${guardStr(cdef.guard)}" + val msg = + if (tree.selector.isEmpty) + em"""cannot reduce implicit match with + | patterns : ${tree.cases.map(patStr).mkString("\n ")}""" + else + em"""cannot reduce inline match with + | scrutinee: $sel : ${selType} + | patterns : ${tree.cases.map(patStr).mkString("\n ")}""" + errorTree(tree, msg) + } + } + override def newLikeThis: Typer = new InlineTyper } /** Drop any side-effect-free bindings that are unused in expansion or other reachable bindings. * Inline def bindings that are used only once. */ - def dropUnusedDefs(bindings: List[ValOrDefDef], tree: Tree)(implicit ctx: Context): (List[ValOrDefDef], Tree) = { + def dropUnusedDefs(bindings: List[MemberDef], tree: Tree)(implicit ctx: Context): (List[MemberDef], Tree) = { + // inlining.println(i"drop unused $bindings%, % in $tree") val refCount = newMutableSymbolMap[Int] - val bindingOfSym = newMutableSymbolMap[ValOrDefDef] + val bindingOfSym = newMutableSymbolMap[MemberDef] val dealiased = new java.util.IdentityHashMap[Type, Type]() - def isInlineable(binding: ValOrDefDef) = binding match { + def isInlineable(binding: MemberDef) = binding match { case DefDef(_, Nil, Nil, _, _) => true case vdef @ ValDef(_, _, _) => isPureExpr(vdef.rhs) case _ => false @@ -697,7 +1028,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { } val dealiasedTermBindings = - termBindings.mapconserve(dealiasTypeBindings.transform).asInstanceOf[List[ValOrDefDef]] + termBindings.mapconserve(dealiasTypeBindings.transform).asInstanceOf[List[MemberDef]] val dealiasedTree = dealiasTypeBindings.transform(tree) val retained = dealiasedTermBindings.filterConserve(binding => retain(binding.symbol)) diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 8e97fce05348..0f6f6201e1e7 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -75,7 +75,11 @@ object ProtoTypes { * achieved by replacing expected type parameters with wildcards. */ def constrainResult(meth: Symbol, mt: Type, pt: Type)(implicit ctx: Context): Boolean = - constrainResult(mt, pt) + if (Inliner.isInlineable(meth)) { + constrainResult(mt, wildApprox(pt)) + true + } + else constrainResult(mt, pt) } object NoViewsAllowed extends Compatibility { diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index cea957340d90..dca8c7137111 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -564,7 +564,8 @@ class Typer extends Namer def typedTpt = checkSimpleKinded(typedType(tree.tpt)) def handlePattern: Tree = { val tpt1 = typedTpt - if (!ctx.isAfterTyper) constrainPatternType(tpt1.tpe, pt)(ctx.addMode(Mode.GADTflexible)) + if (!ctx.isAfterTyper && pt != defn.ImplicitScrutineeTypeRef) + constrainPatternType(tpt1.tpe, pt)(ctx.addMode(Mode.GADTflexible)) // special case for an abstract type that comes with a class tag tryWithClassTag(ascription(tpt1, isWildcard = true), pt) } @@ -705,6 +706,7 @@ class Typer extends Namer } def typedIf(tree: untpd.If, pt: Type)(implicit ctx: Context): Tree = track("typedIf") { + if (tree.isInline) checkInInlineContext("inline if", tree.pos) val cond1 = typed(tree.cond, defn.BooleanType) val thenp2 :: elsep2 :: Nil = harmonic(harmonize, pt) { val thenp1 = typed(tree.thenp, pt.notApplied) @@ -967,19 +969,32 @@ class Typer extends Namer def typedMatch(tree: untpd.Match, pt: Type)(implicit ctx: Context): Tree = track("typedMatch") { tree.selector match { case EmptyTree => - val (protoFormals, _) = decomposeProtoFunction(pt, 1) - val unchecked = pt.isRef(defn.PartialFunctionClass) - typed(desugar.makeCaseLambda(tree.cases, protoFormals.length, unchecked) withPos tree.pos, pt) + if (tree.isInline) { + checkInInlineContext("implicit match", tree.pos) + val cases1 = tree.cases.mapconserve { + case cdef @ CaseDef(pat @ Typed(Ident(nme.WILDCARD), _), _, _) => + // case _ : T --> case evidence$n : T + cpy.CaseDef(cdef)(pat = untpd.Bind(EvidenceParamName.fresh(), pat)) + case cdef => cdef + } + typedMatchFinish(tree, tpd.EmptyTree, defn.ImplicitScrutineeTypeRef, cases1, pt) + } + else { + val (protoFormals, _) = decomposeProtoFunction(pt, 1) + val unchecked = pt.isRef(defn.PartialFunctionClass) + typed(desugar.makeCaseLambda(tree.cases, protoFormals.length, unchecked) withPos tree.pos, pt) + } case _ => + if (tree.isInline) checkInInlineContext("inline match", tree.pos) val sel1 = typedExpr(tree.selector) val selType = fullyDefinedType(sel1.tpe, "pattern selector", tree.pos).widen - typedMatchFinish(tree, sel1, selType, pt) + typedMatchFinish(tree, sel1, selType, tree.cases, pt) } } // Overridden in InlineTyper for inline matches - def typedMatchFinish(tree: untpd.Match, sel: Tree, selType: Type, pt: Type)(implicit ctx: Context): Tree = { - val cases1 = harmonic(harmonize, pt)(typedCases(tree.cases, selType, pt.notApplied)) + def typedMatchFinish(tree: untpd.Match, sel: Tree, wideSelType: Type, cases: List[untpd.CaseDef], pt: Type)(implicit ctx: Context): Tree = { + val cases1 = harmonic(harmonize, pt)(typedCases(cases, wideSelType, pt.notApplied)) .asInstanceOf[List[CaseDef]] assignType(cpy.Match(tree)(sel, cases1), sel, cases1) } @@ -1378,6 +1393,7 @@ class Typer extends Namer if (body1.tpe.isInstanceOf[TermRef]) pt1 else body1.tpe.underlyingIfRepeated(isJava = false) val sym = ctx.newPatternBoundSymbol(tree.name, symTp, tree.pos) + if (pt == defn.ImplicitScrutineeTypeRef) sym.setFlag(Implicit) if (ctx.mode.is(Mode.InPatternAlternative)) ctx.error(i"Illegal variable ${sym.name} in pattern alternative", tree.pos) assignType(cpy.Bind(tree)(tree.name, body1), sym) @@ -2462,8 +2478,8 @@ class Typer extends Namer else if (Inliner.isInlineable(tree) && !ctx.settings.YnoInline.value && !ctx.isAfterTyper && - !ctx.reporter.hasErrors && - tree.tpe <:< pt) { + !ctx.reporter.hasErrors) { + tree.tpe <:< wildApprox(pt) readaptSimplified(Inliner.inlineCall(tree, pt)) } else if (tree.tpe <:< pt) { diff --git a/compiler/test/dotc/pos-test-pickling.blacklist b/compiler/test/dotc/pos-test-pickling.blacklist index 60aad09463c7..22a41321bf77 100644 --- a/compiler/test/dotc/pos-test-pickling.blacklist +++ b/compiler/test/dotc/pos-test-pickling.blacklist @@ -11,6 +11,7 @@ i3976.scala i4125.scala implicit-dep.scala inline-access-levels +inline-rewrite.scala macro-with-array macro-with-type matchtype.scala @@ -43,3 +44,4 @@ t7264 t7532b t8062 typeclass-encoding2.scala +typelevel0.scala diff --git a/compiler/test/dotc/run-from-tasty.blacklist b/compiler/test/dotc/run-from-tasty.blacklist index fe0acf63becb..ad83b4eb8b8b 100644 --- a/compiler/test/dotc/run-from-tasty.blacklist +++ b/compiler/test/dotc/run-from-tasty.blacklist @@ -3,3 +3,6 @@ eff-dependent.scala # It seems we still harmonize types in fromTasty. Not sure where this happens puzzle.scala + +# Need to print empty tree for implicit match +implicitMatch.scala diff --git a/compiler/test/dotc/run-test-pickling.blacklist b/compiler/test/dotc/run-test-pickling.blacklist index db1a51b342b3..4e30708a3def 100644 --- a/compiler/test/dotc/run-test-pickling.blacklist +++ b/compiler/test/dotc/run-test-pickling.blacklist @@ -8,3 +8,4 @@ t8133 t8133b tuples1.scala tuples1a.scala +implicitMatch.scala diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index 14eda9116940..d83734dc0854 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -177,7 +177,8 @@ Expr1 ::= ‘if’ ‘(’ Expr ‘)’ {nl} | [SimpleExpr ‘.’] id ‘=’ Expr Assign(expr, expr) | SimpleExpr1 ArgumentExprs ‘=’ Expr Assign(expr, expr) | PostfixExpr [Ascription] - | PostfixExpr ‘match’ ‘{’ CaseClauses ‘}’ Match(expr, cases) -- point on match + | [‘inline’] PostfixExpr ‘match’ ‘{’ CaseClauses ‘}’ Match(expr, cases) -- point on match + | ‘implicit’ ‘match’ ‘{’ ImplicitCaseClauses ‘}’ Ascription ::= ‘:’ InfixType Typed(expr, tp) | ‘:’ Annotation {Annotation} Typed(expr, Annotated(EmptyTree, annot)*) Catches ::= ‘catch’ Expr @@ -226,6 +227,8 @@ Guard ::= ‘if’ PostfixExpr CaseClauses ::= CaseClause { CaseClause } Match(EmptyTree, cases) CaseClause ::= ‘case’ (Pattern [Guard] ‘=>’ Block | INT) CaseDef(pat, guard?, block) // block starts at => +ImplicitCaseClauses ::= ImplicitCaseClause { ImplicitCaseClause } +ImplicitCaseClause ::= ‘case’ PatVar [‘:’ RefinedType] [Guard] ‘=>’ Block TypeCaseClauses ::= TypeCaseClause { TypeCaseClause } TypeCaseClause ::= ‘case’ InfixType ‘=>’ Type [nl] diff --git a/library/src-scala3/scala/StagedTuple.scala b/library/src-scala3/scala/StagedTuple.scala index 68fd9ab4980f..96ef7ab31e79 100644 --- a/library/src-scala3/scala/StagedTuple.scala +++ b/library/src-scala3/scala/StagedTuple.scala @@ -6,6 +6,8 @@ object StagedTuple { import Tuple._ import NonEmptyTuple._ + private final val specialize = true + def toArrayStaged(tup: Expr[Tuple], size: Option[Int]): Expr[Array[Object]] = { if (!specialize) '(dynamicToArray(~tup)) else size match { diff --git a/library/src-scala3/scala/Tuple.scala b/library/src-scala3/scala/Tuple.scala index 3a6358f9c7aa..3f19b82d4a43 100644 --- a/library/src-scala3/scala/Tuple.scala +++ b/library/src-scala3/scala/Tuple.scala @@ -1,32 +1,134 @@ package scala import annotation.showAsInfix import typelevel._ +import scala.StagedTuple sealed trait Tuple extends Any { import Tuple._ - import StagedTuple._ inline def toArray: Array[Object] = - ~toArrayStaged('(this), constValueOpt[BoundedSize[this.type]]) + if (stageIt) stagedToArray + else inline constValueOpt[BoundedSize[this.type]] match { + case Some(0) => + $emptyArray + case Some(1) => + val t = asInstanceOf[Tuple1[Object]] + Array(t._1) + case Some(2) => + val t = asInstanceOf[Tuple2[Object, Object]] + Array(t._1, t._2) + case Some(3) => + val t = asInstanceOf[Tuple3[Object, Object, Object]] + Array(t._1, t._2, t._3) + case Some(4) => + val t = asInstanceOf[Tuple4[Object, Object, Object, Object]] + Array(t._1, t._2, t._3, t._4) + case Some(n) if n <= $MaxSpecialized => + $toArray(this, n) + case Some(n) => + asInstanceOf[TupleXXL].elems + case None => + dynamicToArray(this) + } + + inline def stagedToArray: Array[Object] = + ~StagedTuple.toArrayStaged('(this), constValueOpt[BoundedSize[this.type]]) inline def *: [H] (x: H): H *: this.type = - ~stagedCons('(this), '(x), constValueOpt[BoundedSize[this.type]]) + if (stageIt) stagedCons[H](x) + else { + type Result = H *: this.type + inline constValueOpt[BoundedSize[this.type]] match { + case Some(0) => + Tuple1(x).asInstanceOf[Result] + case Some(1) => + Tuple2(x, asInstanceOf[Tuple1[_]]._1).asInstanceOf[Result] + case Some(2) => + val t = asInstanceOf[Tuple2[_, _]] + Tuple3(x, t._1, t._2).asInstanceOf[Result] + case Some(3) => + val t = asInstanceOf[Tuple3[_, _, _]] + Tuple4(x, t._1, t._2, t._3).asInstanceOf[Result] + case Some(4) => + val t = asInstanceOf[Tuple4[_, _, _, _]] + Tuple5(x, t._1, t._2, t._3, t._4).asInstanceOf[Result] + case Some(n) => + fromArray[Result]($consArray(x, toArray)) + case _ => + dynamic_*:[this.type, H](this, x) + } + } + + inline def stagedCons[H] (x: H): H *: this.type = + ~StagedTuple.stagedCons('(this), '(x), constValueOpt[BoundedSize[this.type]]) inline def ++(that: Tuple): Concat[this.type, that.type] = - ~stagedConcat('(this), constValueOpt[BoundedSize[this.type]], '(that), constValueOpt[BoundedSize[that.type]]) + if (stageIt) stagedConcat(that) + else { + type Result = Concat[this.type, that.type] + inline constValueOpt[BoundedSize[this.type]] match { + case Some(0) => + that.asInstanceOf[Result] + case Some(1) => + if (constValue[BoundedSize[that.type]] == 0) this.asInstanceOf[Result] + else (asInstanceOf[Tuple1[_]]._1 *: that).asInstanceOf[Result] + case Some(2) => + val t = asInstanceOf[Tuple2[_, _]] + inline constValue[BoundedSize[that.type]] match { + case 0 => this.asInstanceOf[Result] + case 1 => + val u = that.asInstanceOf[Tuple1[_]] + Tuple3(t._1, t._2, u._1).asInstanceOf[Result] + case 2 => + val u = that.asInstanceOf[Tuple2[_, _]] + Tuple4(t._1, t._2, u._1, u._2).asInstanceOf[Result] + case _ => + genericConcat[Result](this, that).asInstanceOf[Result] + } + case Some(3) => + val t = asInstanceOf[Tuple3[_, _, _]] + inline constValue[BoundedSize[that.type]] match { + case 0 => this.asInstanceOf[Result] + case 1 => + val u = that.asInstanceOf[Tuple1[_]] + Tuple4(t._1, t._2, t._3, u._1).asInstanceOf[Result] + case _ => + genericConcat[Result](this, that).asInstanceOf[Result] + } + case Some(_) => + if (constValue[BoundedSize[that.type]] == 0) this.asInstanceOf[Result] + else genericConcat[Result](this, that).asInstanceOf[Result] + case None => + dynamic_++[this.type, that.type](this, that) + } + } + + inline def stagedConcat(that: Tuple): Concat[this.type, that.type] = + ~StagedTuple.stagedConcat('(this), constValueOpt[BoundedSize[this.type]], + '(that), constValueOpt[BoundedSize[that.type]]) + + inline def genericConcat[T <: Tuple](xs: Tuple, ys: Tuple): Tuple = + fromArray[T](xs.toArray ++ ys.toArray) inline def size: Size[this.type] = - ~sizeStaged[Size[this.type]]('(this), constValueOpt[BoundedSize[this.type]]) + if (stageIt) stagedSize + else { + type Result = Size[this.type] + inline constValueOpt[BoundedSize[this.type]] match { + case Some(n) => n.asInstanceOf[Result] + case _ => dynamicSize(this) + } + } + inline def stagedSize: Size[this.type] = + ~StagedTuple.sizeStaged[Size[this.type]]('(this), constValueOpt[BoundedSize[this.type]]) } object Tuple { - import StagedTuple._ - inline val $MaxSpecialized = 22 inline private val XXL = $MaxSpecialized + 1 - final val specialize = true + final val stageIt = false type Head[+X <: NonEmptyTuple] = X match { case x *: _ => x @@ -86,7 +188,36 @@ object Tuple { } inline def fromArray[T <: Tuple](xs: Array[Object]): T = - ~fromArrayStaged[T]('(xs), constValueOpt[BoundedSize[this.type]]) + if (stageIt) stagedFromArray[T](xs) + else inline constValue[BoundedSize[T]] match { + case 0 => ().asInstanceOf[T] + case 1 => Tuple1(xs(0)).asInstanceOf[T] + case 2 => Tuple2(xs(0), xs(1)).asInstanceOf[T] + case 3 => Tuple3(xs(0), xs(1), xs(2)).asInstanceOf[T] + case 4 => Tuple4(xs(0), xs(1), xs(2), xs(3)).asInstanceOf[T] + case 5 => Tuple5(xs(0), xs(1), xs(2), xs(3), xs(4)).asInstanceOf[T] + case 6 => Tuple6(xs(0), xs(1), xs(2), xs(3), xs(4), xs(5)).asInstanceOf[T] + case 7 => Tuple7(xs(0), xs(1), xs(2), xs(3), xs(4), xs(5), xs(6)).asInstanceOf[T] + case 8 => Tuple8(xs(0), xs(1), xs(2), xs(3), xs(4), xs(5), xs(6), xs(7)).asInstanceOf[T] + case 9 => Tuple9(xs(0), xs(1), xs(2), xs(3), xs(4), xs(5), xs(6), xs(7), xs(8)).asInstanceOf[T] + case 10 => Tuple10(xs(0), xs(1), xs(2), xs(3), xs(4), xs(5), xs(6), xs(7), xs(8), xs(9)).asInstanceOf[T] + case 11 => Tuple11(xs(0), xs(1), xs(2), xs(3), xs(4), xs(5), xs(6), xs(7), xs(8), xs(9), xs(10)).asInstanceOf[T] + case 12 => Tuple12(xs(0), xs(1), xs(2), xs(3), xs(4), xs(5), xs(6), xs(7), xs(8), xs(9), xs(10), xs(11)).asInstanceOf[T] + case 13 => Tuple13(xs(0), xs(1), xs(2), xs(3), xs(4), xs(5), xs(6), xs(7), xs(8), xs(9), xs(10), xs(11), xs(12)).asInstanceOf[T] + case 14 => Tuple14(xs(0), xs(1), xs(2), xs(3), xs(4), xs(5), xs(6), xs(7), xs(8), xs(9), xs(10), xs(11), xs(12), xs(13)).asInstanceOf[T] + case 15 => Tuple15(xs(0), xs(1), xs(2), xs(3), xs(4), xs(5), xs(6), xs(7), xs(8), xs(9), xs(10), xs(11), xs(12), xs(13), xs(14)).asInstanceOf[T] + case 16 => Tuple16(xs(0), xs(1), xs(2), xs(3), xs(4), xs(5), xs(6), xs(7), xs(8), xs(9), xs(10), xs(11), xs(12), xs(13), xs(14), xs(15)).asInstanceOf[T] + case 17 => Tuple17(xs(0), xs(1), xs(2), xs(3), xs(4), xs(5), xs(6), xs(7), xs(8), xs(9), xs(10), xs(11), xs(12), xs(13), xs(14), xs(15), xs(16)).asInstanceOf[T] + case 18 => Tuple18(xs(0), xs(1), xs(2), xs(3), xs(4), xs(5), xs(6), xs(7), xs(8), xs(9), xs(10), xs(11), xs(12), xs(13), xs(14), xs(15), xs(16), xs(17)).asInstanceOf[T] + case 19 => Tuple19(xs(0), xs(1), xs(2), xs(3), xs(4), xs(5), xs(6), xs(7), xs(8), xs(9), xs(10), xs(11), xs(12), xs(13), xs(14), xs(15), xs(16), xs(17), xs(18)).asInstanceOf[T] + case 20 => Tuple20(xs(0), xs(1), xs(2), xs(3), xs(4), xs(5), xs(6), xs(7), xs(8), xs(9), xs(10), xs(11), xs(12), xs(13), xs(14), xs(15), xs(16), xs(17), xs(18), xs(19)).asInstanceOf[T] + case 21 => Tuple21(xs(0), xs(1), xs(2), xs(3), xs(4), xs(5), xs(6), xs(7), xs(8), xs(9), xs(10), xs(11), xs(12), xs(13), xs(14), xs(15), xs(16), xs(17), xs(18), xs(19), xs(20)).asInstanceOf[T] + case 22 => Tuple22(xs(0), xs(1), xs(2), xs(3), xs(4), xs(5), xs(6), xs(7), xs(8), xs(9), xs(10), xs(11), xs(12), xs(13), xs(14), xs(15), xs(16), xs(17), xs(18), xs(19), xs(20), xs(21)).asInstanceOf[T] + case _ => TupleXXL(xs).asInstanceOf[T] + } + + inline def stagedFromArray[T <: Tuple](xs: Array[Object]): T = + ~StagedTuple.fromArrayStaged[T]('(xs), constValueOpt[BoundedSize[this.type]]) def dynamicFromArray[T <: Tuple](xs: Array[Object]): T = xs.length match { case 0 => ().asInstanceOf[T] @@ -179,17 +310,128 @@ object Tuple { abstract sealed class NonEmptyTuple extends Tuple { import Tuple._ import NonEmptyTuple._ - import StagedTuple._ inline def head: Head[this.type] = - ~headStaged[this.type]('(this), constValueOpt[BoundedSize[this.type]]) + if (stageIt) stagedHead + else { + type Result = Head[this.type] + val resVal = inline constValueOpt[BoundedSize[this.type]] match { + case Some(1) => + val t = asInstanceOf[Tuple1[_]] + t._1 + case Some(2) => + val t = asInstanceOf[Tuple2[_, _]] + t._1 + case Some(3) => + val t = asInstanceOf[Tuple3[_, _, _]] + t._1 + case Some(4) => + val t = asInstanceOf[Tuple4[_, _, _, _]] + t._1 + case Some(n) if n > 4 && n <= $MaxSpecialized => + asInstanceOf[Product].productElement(0) + case Some(n) if n > $MaxSpecialized => + val t = asInstanceOf[TupleXXL] + t.elems(0) + case None => + dynamicHead[this.type](this) + } + resVal.asInstanceOf[Result] + } + + inline def stagedHead: Head[this.type] = + ~StagedTuple.headStaged[this.type]('(this), constValueOpt[BoundedSize[this.type]]) inline def tail: Tail[this.type] = - ~tailStaged[this.type]('(this), constValueOpt[BoundedSize[this.type]]) + if (stageIt) stagedTail + else { + type Result = Tail[this.type] + inline constValueOpt[BoundedSize[this.type]] match { + case Some(1) => + ().asInstanceOf[Result] + case Some(2) => + val t = asInstanceOf[Tuple2[_, _]] + Tuple1(t._2).asInstanceOf[Result] + case Some(3) => + val t = asInstanceOf[Tuple3[_, _, _]] + Tuple2(t._2, t._3).asInstanceOf[Result] + case Some(4) => + val t = asInstanceOf[Tuple4[_, _, _, _]] + Tuple3(t._2, t._3, t._4).asInstanceOf[Result] + case Some(5) => + val t = asInstanceOf[Tuple5[_, _, _, _, _]] + Tuple4(t._2, t._3, t._4, t._5).asInstanceOf[Result] + case Some(n) if n > 5 => + fromArray[Result](toArray.tail) + case None => + dynamicTail[this.type](this) + } + } + + inline def stagedTail: Tail[this.type] = + ~StagedTuple.tailStaged[this.type]('(this), constValueOpt[BoundedSize[this.type]]) + + inline def fallbackApply(n: Int) = + inline constValueOpt[n.type] match { + case Some(n: Int) => error("index out of bounds: ", n) + case None => dynamicApply[this.type](this, n) + } inline def apply(n: Int): Elem[this.type, n.type] = - ~applyStaged[this.type, n.type]('(this), constValueOpt[Size[this.type]], '(n), constValueOpt[n.type]) + if (stageIt) stagedApply(n) + else { + type Result = Elem[this.type, n.type] + inline constValueOpt[Size[this.type]] match { + case Some(1) => + val t = asInstanceOf[Tuple1[_]] + inline constValueOpt[n.type] match { + case Some(0) => t._1.asInstanceOf[Result] + case _ => fallbackApply(n).asInstanceOf[Result] + } + case Some(2) => + val t = asInstanceOf[Tuple2[_, _]] + inline constValueOpt[n.type] match { + case Some(0) => t._1.asInstanceOf[Result] + case Some(1) => t._2.asInstanceOf[Result] + case _ => fallbackApply(n).asInstanceOf[Result] + } + case Some(3) => + val t = asInstanceOf[Tuple3[_, _, _]] + inline constValueOpt[n.type] match { + case Some(0) => t._1.asInstanceOf[Result] + case Some(1) => t._2.asInstanceOf[Result] + case Some(2) => t._3.asInstanceOf[Result] + case _ => fallbackApply(n).asInstanceOf[Result] + } + case Some(4) => + val t = asInstanceOf[Tuple4[_, _, _, _]] + inline constValueOpt[n.type] match { + case Some(0) => t._1.asInstanceOf[Result] + case Some(1) => t._2.asInstanceOf[Result] + case Some(2) => t._3.asInstanceOf[Result] + case Some(3) => t._4.asInstanceOf[Result] + case _ => fallbackApply(n).asInstanceOf[Result] + } + case Some(s) if s > 4 && s <= $MaxSpecialized => + val t = asInstanceOf[Product] + inline constValueOpt[n.type] match { + case Some(n) if n >= 0 && n < s => t.productElement(n).asInstanceOf[Result] + case _ => fallbackApply(n).asInstanceOf[Result] + } + case Some(s) if s > $MaxSpecialized => + val t = asInstanceOf[TupleXXL] + inline constValueOpt[n.type] match { + case Some(n) if n >= 0 && n < s => t.elems(n).asInstanceOf[Result] + case _ => fallbackApply(n).asInstanceOf[Result] + } + case _ => fallbackApply(n).asInstanceOf[Result] + } + } + inline def stagedApply(n: Int): Elem[this.type, n.type] = + ~StagedTuple.applyStaged[this.type, n.type]( + '(this), constValueOpt[Size[this.type]], + '(n), constValueOpt[n.type]) } object NonEmptyTuple { @@ -234,6 +476,5 @@ object NonEmptyTuple { sealed class *:[+H, +T <: Tuple] extends NonEmptyTuple object *: { - def unapply[H, T <: Tuple](x: H *: T): (H, T) = - (NonEmptyTuple.dynamicHead(x), NonEmptyTuple.dynamicTail(x)) + inline def unapply[H, T <: Tuple](x: H *: T) = (x.head, x.tail) } diff --git a/tests/invalid/neg/typelevel.scala b/tests/invalid/neg/typelevel.scala index a0aad695393b..32d270b965a9 100644 --- a/tests/invalid/neg/typelevel.scala +++ b/tests/invalid/neg/typelevel.scala @@ -1,3 +1,4 @@ +// This test requires retyping from untyped trees after inlining, which is not supported anymore trait HList { def length: Int def head: Any diff --git a/tests/invalid/pos/transparent-overload.scala b/tests/invalid/pos/transparent-overload.scala deleted file mode 100644 index 6410fa4d6702..000000000000 --- a/tests/invalid/pos/transparent-overload.scala +++ /dev/null @@ -1,22 +0,0 @@ -object Test { - - def f(x: Int): Int = x - def f(x: String): String = x - def f(x: Any): Any = x - - inline def g(x: Any) = f(x) - inline def h(x: Any) = this.f(x) - - locally { - val x1 = g(1) - val x2 = g("bb") - val y1: Int = x1 - val y2: String = x2 - } - locally { - val x1 = h(1) - val x2 = h("bb") - val y1: Int = x1 - val y2: String = x2 - } -} \ No newline at end of file diff --git a/tests/invalid/pos/typelevel-vector1.scala b/tests/invalid/pos/typelevel-vector1.scala deleted file mode 100644 index 64386cc7de2a..000000000000 --- a/tests/invalid/pos/typelevel-vector1.scala +++ /dev/null @@ -1,30 +0,0 @@ -object typelevel { - case class Typed[T](value: T) { type Type = T } - erased def erasedValue[T]: T = ??? -} - -trait Nat -case object Z extends Nat -case class S[N <: Nat](n: N) extends Nat - -case class Vec[T, N <: Nat](elems: List[T]) - -object Test { - import typelevel._ - type Z = Z.type - - inline def add(x: Nat, y: Nat): Nat = inline x match { - case Z => y - case S(x1) => S(add(x1, y)) - } - - val x = S(S(Z)) - val y = add(x, x) - val z: S[S[S[S[Z]]]] = y - - inline def concat[T, N1 <: Nat, N2 <: Nat](xs: Vec[T, N1], ys: Vec[T, N2]): Vec[T, _] = { - val length = Typed(add(erasedValue[N1], erasedValue[N2])) - Vec[T, length.Type](xs.elems ++ ys.elems) - } -} - diff --git a/tests/invalid/run/Tuple.scala b/tests/invalid/run/Tuple.scala index 009443690a3b..00a9b545e77d 100644 --- a/tests/invalid/run/Tuple.scala +++ b/tests/invalid/run/Tuple.scala @@ -1,5 +1,6 @@ import annotation.showAsInfix +// This version of Tuple requires full retyping of untyped trees on inlining object typelevel { erased def erasedValue[T]: T = ??? class Typed[T](val value: T) { type Type = T } diff --git a/tests/invalid/run/typelevel-patmat.scala b/tests/invalid/run/typelevel-patmat.scala index b38977b8546d..62c5225479e1 100644 --- a/tests/invalid/run/typelevel-patmat.scala +++ b/tests/invalid/run/typelevel-patmat.scala @@ -1,4 +1,4 @@ - +// This test requires retyping from untyped trees after inlining, which is not supported anymore object typelevel { case class Typed[T](value: T) { type Type = T } } @@ -21,7 +21,7 @@ object Test extends App { type HNil = HNil.type type Z = Z.type - inline def ToNat(inline n: Int): Typed[Nat] = + inline def ToNat(inline n: Int) <: Typed[Nat] = if n == 0 then Typed(Z) else Typed(S(ToNat(n - 1).value)) @@ -35,7 +35,7 @@ object Test extends App { println(x1) println(x2) - inline def toInt(n: Nat): Int = inline n match { + inline def toInt(n: Nat) <: Int = inline n match { case Z => 0 case S(n1) => toInt(n1) + 1 } @@ -45,7 +45,7 @@ object Test extends App { inline val i2 = toInt(y2) val j2: 2 = i2 - inline def concat(xs: HList, ys: HList): HList = inline xs match { + inline def concat(xs: HList, ys: HList) <: HList = inline xs match { case HNil => ys case HCons(x, xs1) => HCons(x, concat(xs1, ys)) } @@ -68,7 +68,7 @@ object Test extends App { val r6 = concat(HCons(1, HCons("a", HNil)), HCons(true, HCons(1.0, HNil))) val c6: HCons[Int, HCons[String, HCons[Boolean, HCons[Double, HNil]]]] = r6 - inline def nth(xs: HList, n: Int): Any = inline xs match { + inline def nth(xs: HList, n: Int) <: Any = inline xs match { case HCons(x, _) if n == 0 => x case HCons(_, xs1) if n > 0 => nth(xs1, n - 1) } @@ -78,12 +78,12 @@ object Test extends App { val e1 = nth(r2, 1) val ce1: String = e1 - inline def concatTyped(xs: HList, ys: HList): Typed[_ <: HList] = inline xs match { + inline def concatTyped(xs: HList, ys: HList) <: Typed[_ <: HList] = inline xs match { case HNil => Typed(ys) case HCons(x, xs1) => Typed(HCons(x, concatTyped(xs1, ys).value)) } - def concatImpl(xs: HList, ys: HList): HList = xs match { + def concatImpl(xs: HList, ys: HList) <: HList = xs match { case HNil => ys case HCons(x, xs1) => HCons(x, concatImpl(xs1, ys)) } diff --git a/tests/invalid/run/typelevel1.scala b/tests/invalid/run/typelevel1.scala index 376791ba9b15..8324c5c58df9 100644 --- a/tests/invalid/run/typelevel1.scala +++ b/tests/invalid/run/typelevel1.scala @@ -1,22 +1,22 @@ - +// This test requires retyping from untyped trees after inlining, which is not supported anymore trait HList { def length: Int = ??? def head: Any def tail: HList - inline def isEmpty: Boolean = length == 0 + inline def isEmpty <: Boolean = length == 0 } case object HNil extends HList { - inline override def length = 0 + inline override def length <: Int = 0 def head: Nothing = ??? def tail: Nothing = ??? } case class :: [H, T <: HList] (hd: H, tl: T) extends HList { - inline override def length = 1 + tl.length - def head: H = this.hd - def tail: T = this.tl + inline override def length <: Int = 1 + tl.length + inline def head: H = this.hd + inline def tail: T = this.tl } object Test extends App { @@ -30,8 +30,10 @@ object Test extends App { inline implicit def hlistDeco(xs: HList): HListDeco = new HListDeco(xs) - inline def concat[T1, T2](xs: HList, ys: HList): HList = - if xs.isEmpty then ys + // Does not work since it infers `Any` as a type argument for `::` + // and we cannot undo that without a typing from untyped. + inline def concat[T1, T2](xs: HList, ys: HList) <: HList = + inline if xs.isEmpty then ys else new ::(xs.head, concat(xs.tail, ys)) val xs = 1 :: "a" :: "b" :: HNil diff --git a/tests/invalid/run/typelevel3.scala b/tests/invalid/run/typelevel3.scala index c53270e2d4f6..a8721af2517f 100644 --- a/tests/invalid/run/typelevel3.scala +++ b/tests/invalid/run/typelevel3.scala @@ -1,4 +1,4 @@ - +// This test requires retyping from untyped trees after inlining, which is not supported anymore trait HList { def length: Int = 0 def head: Any diff --git a/tests/neg/implicitMatch-syntax.scala b/tests/neg/implicitMatch-syntax.scala new file mode 100644 index 000000000000..2df0357a01da --- /dev/null +++ b/tests/neg/implicitMatch-syntax.scala @@ -0,0 +1,33 @@ +object Test { + import collection.immutable.TreeSet + import collection.immutable.HashSet + + inline def f1[T] = implicit implicit match { // error: repeated modifier // error: illegal modifier + case ord: Ordered[T] => new TreeSet[T] // error: no implicit + case _ => new HashSet[T] + + } + + inline def f2[T] = implicit erased match { // error: illegal modifier + case ord: Ordered[T] => new TreeSet[T] // error: no implicit + case _ => new HashSet[T] + } + + inline def f3[T] = erased implicit match { // error: illegal modifier + case ord: Ordered[T] => new TreeSet[T] // error: no implicit + case _ => new HashSet[T] + } + + inline def f4() = implicit match { + case Nil => ??? // error: not a legal pattern + case x :: xs => ??? // error: not a legal pattern + } + + inline def f5[T] = locally { implicit match { // Ok + case _ => new HashSet[T] + }} + + def f6[T] = implicit match { // error: implicit match cannot be used here + case _ => new HashSet[T] + } +} \ No newline at end of file diff --git a/tests/neg/inline-overload.scala b/tests/neg/inline-overload.scala new file mode 100644 index 000000000000..a8cd4c649189 --- /dev/null +++ b/tests/neg/inline-overload.scala @@ -0,0 +1,24 @@ +// This test demonstrates that overloading resulution is not redone when +// inlining. +object Test { + + def f(x: Int): Int = x + def f(x: String): String = x + def f(x: Any): Any = x + + inline def g(x: Any) = f(x) + inline def h(x: Any) = this.f(x) + + locally { + val x1 = g(1) + val x2 = g("bb") + val y1: Int = x1 // error: type mismatch + val y2: String = x2 // error: type mismatch + } + locally { + val x1 = h(1) + val x2 = h("bb") + val y1: Int = x1 // error: type mismatch + val y2: String = x2 // error: type mismatch + } +} \ No newline at end of file diff --git a/tests/neg/inline-unapply.scala b/tests/neg/inline-unapply.scala new file mode 100644 index 000000000000..98fdf3ad4b12 --- /dev/null +++ b/tests/neg/inline-unapply.scala @@ -0,0 +1,15 @@ +object Test { + + class C(val x: Int, val y: Int) + + inline def unapply(c: C): Option[(Int, Int)] = Some((c.x, c.y)) // ok + +} +object Test2 { + + class C(x: Int, y: Int) + + inline def unapply(c: C): Option[(Int, Int)] = inline c match { // error: inline match cannot be used in an inline unapply + case x: C => (1, 1) + } +} \ No newline at end of file diff --git a/tests/neg/nested-rewrites.scala b/tests/neg/nested-rewrites.scala index 3dcce36182dc..d8d3e0e53f28 100644 --- a/tests/neg/nested-rewrites.scala +++ b/tests/neg/nested-rewrites.scala @@ -15,14 +15,14 @@ object Test { object Test0 { def f(x: Int) = { - inline def g(x: Int) = x match { + inline def g(x: Int) = inline x match { case 0 => 0 } g(0) inline val Y = 0 g(Y) - inline def h(x: Int) = x match { + inline def h(x: Int) = inline x match { case Y => 0 } h(0) @@ -35,14 +35,14 @@ object Test0 { object Test1 { erased inline def f(x: Int) = { - erased inline def g(x: Int) = x match { // error: implementation restriction: nested inline methods are not supported + erased inline def g(x: Int) = inline x match { // error: implementation restriction: nested inline methods are not supported case 0 => 0 } g(0) inline val Y = 0 g(Y) - inline def h(x: Int) = x match { // error: implementation restriction: nested inline methods are not supported + inline def h(x: Int) = inline x match { // error: implementation restriction: nested inline methods are not supported case Y => 0 } h(0) diff --git a/tests/neg/transparent-override/B_2.scala b/tests/neg/transparent-override/B_2.scala index b24bf7d5345d..5c83c7f5e75b 100644 --- a/tests/neg/transparent-override/B_2.scala +++ b/tests/neg/transparent-override/B_2.scala @@ -1,5 +1,5 @@ class B extends A { - inline def f(x: Int): Int = x match { // error + inline def f(x: Int): Int = inline x match { // error case 0 => 1 case _ => x } diff --git a/tests/disabled/neg/tuple-oob1.scala b/tests/neg/tuple-oob1.scala similarity index 100% rename from tests/disabled/neg/tuple-oob1.scala rename to tests/neg/tuple-oob1.scala diff --git a/tests/neg/typelevel-noeta.scala b/tests/neg/typelevel-noeta.scala index 1641678310de..547a4ed0e6c1 100644 --- a/tests/neg/typelevel-noeta.scala +++ b/tests/neg/typelevel-noeta.scala @@ -2,12 +2,12 @@ object Test { def anyValue[T]: T = ??? - inline def test(x: Int) = x match { + inline def test(x: Int) = inline x match { case _: Byte => case _: Char => } - inline def test2() = 1 match { + inline def test2() = inline 1 match { case _: Byte => case _: Char => } diff --git a/tests/pos-deep-subtype/tuples23.check b/tests/pos-deep-subtype/tuples23.check new file mode 100644 index 000000000000..7b28abb0478c --- /dev/null +++ b/tests/pos-deep-subtype/tuples23.check @@ -0,0 +1,2 @@ +(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23) +276 diff --git a/tests/pos-deep-subtype/tuples23.scala b/tests/pos-deep-subtype/tuples23.scala index 5e7825fc5ac6..3cf4346ac3b0 100644 --- a/tests/pos-deep-subtype/tuples23.scala +++ b/tests/pos-deep-subtype/tuples23.scala @@ -14,7 +14,8 @@ object Test extends App { case (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22, x23) => println(x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9 + x10 + x11 + x12 + x13 + x14 + x15 + x16 + x17 + x18 + x19 + x20 + x21 + x22 + x23) } - inline def decompose3 = x23 match { case x *: y *: xs => (x, y, xs) } + /* TODO: re-enable + inline def decompose3 = inline x23 match { case x *: y *: xs => (x, y, xs) } { val (x, y, xs) = decompose3 val xc: Int = x @@ -22,6 +23,7 @@ object Test extends App { val xsc: Unit = xs println(s"$x23 -> $x, $y, $xs") } + */ val x23s: 23 = x23.size } \ No newline at end of file diff --git a/tests/pos/i2166.scala b/tests/pos/i2166.scala index f796031d009d..28cc05573d29 100644 --- a/tests/pos/i2166.scala +++ b/tests/pos/i2166.scala @@ -1,5 +1,5 @@ object Test { - inline def f = "" match { case _ => false } + inline def f = inline "" match { case _ => false } def main(args: Array[String]): Unit = f } \ No newline at end of file diff --git a/tests/pos/inline-caseclass.scala b/tests/pos/inline-caseclass.scala new file mode 100644 index 000000000000..e7dd05559654 --- /dev/null +++ b/tests/pos/inline-caseclass.scala @@ -0,0 +1,19 @@ +trait Nat +case object Z extends Nat +case class S[N <: Nat](n: N) extends Nat + +object Test { + type Z = Z.type + + inline def add(x: Nat, y: Int) <: Int = inline x match { + case Z => y + case S(x1) => add(x1, y) + 1 + } + + val x = S(S(Z)) + val a: 2 = add(Z, 2) + inline val y = add(x, 2) + val z: 4 = y + +} + diff --git a/tests/neg/inline-i1773.scala b/tests/pos/inline-i1773.scala similarity index 71% rename from tests/neg/inline-i1773.scala rename to tests/pos/inline-i1773.scala index 54dcd4ef8425..f66b96f09b50 100644 --- a/tests/neg/inline-i1773.scala +++ b/tests/pos/inline-i1773.scala @@ -7,7 +7,7 @@ object Test { } def main(args: Array[String]): Unit = { - val q"class $name extends $parent" = new Object // error: method unapply is used + val q"class $name extends $parent" = new Object // now ok, was an error that "method unapply is used" println(name) println(parent) } diff --git a/tests/pos/inline-rewrite.scala b/tests/pos/inline-rewrite.scala new file mode 100644 index 000000000000..3214fa6ad1cc --- /dev/null +++ b/tests/pos/inline-rewrite.scala @@ -0,0 +1,20 @@ +object Test { + + inline def f(x: Int) = inline x match { + case 1 => "a" + case 2 => 22 + } + + val x: String = f(1) + val y = f(2) + val yc: Int = y + + inline def g(x: Any) = inline x match { + case x: String => (x, x) + case x: Double => x + } + + val a: (String, String) = g("") + val b: Double = g(1.0) + +} \ No newline at end of file diff --git a/tests/pos/test.sc b/tests/pos/test.sc deleted file mode 100644 index 8cad24ab31ca..000000000000 --- a/tests/pos/test.sc +++ /dev/null @@ -1,87 +0,0 @@ -val x = 22 - -val y = x * 2 -def square(x: Double) = x * x -square(33) - -class Bar -type L = scala.List - -def zip[T, U](xs: List[T], ys: List[U]) = { - val (ps, _) = xs.foldLeft((Nil: List[(T, U)], ys)) { - (acc, x) => - val (ps, ys) = acc - ys match { - case Nil => acc - case y :: ys => ((x, y) :: ps, ys) - } - } - ps.reverse - } -zip(List(1, 2, 3, 4), List("a", "b", "c")) - -def zips(xss: List[List[Int]]): List[List[Int]] = { - if (xss.forall(_.nonEmpty)) - xss.map(_.head) :: zips(xss.map(_.tail)) - else Nil -} - -zips( - List( - List(1, 2, 3), - List(11, 22), - List(111, 222, 333, 444) - )) - -abstract class A { - def foo: Any -} -abstract class B extends A { - def foo: Int -} -abstract class C extends A { - def foo: Int -} -def f: A | B = ??? -def g = f.foo - - -val xx = 22 - -trait T { - def apply(x: Int): Unit = () -} -class CC extends T { - override def apply(x: Int) = super.apply(1) -} -object o { - def apply(): String = "" -} -val s: String = o() - -Double.NaN.equals(Double.NaN) - -trait Status -case object A extends Status -case object B extends Status - -if (true) A else B - -case class Wrapper(i: Int) - -trait Functor[F[_]] { - def map[A, B](x: F[A], f: A => B): F[B] -} - -class Deriver[F[_]] { - def foldLeft[A, B](result: A, value: B): A -} - -trait Eq[A] { - def equals(x: A, y: A): Boolean -} - -trait DC { type TT } - -def m(x: DC): x.TT = ??? -val meta = m \ No newline at end of file diff --git a/tests/pos/typelevel0.scala b/tests/pos/typelevel0.scala new file mode 100644 index 000000000000..d933e50bcb50 --- /dev/null +++ b/tests/pos/typelevel0.scala @@ -0,0 +1,45 @@ + +trait HList { + def length: Int = ??? + def head: Any + def tail: HList + + inline def isEmpty <: Boolean = length == 0 +} + +case object HNil extends HList { + inline override def length <: Int = 0 + def head: Nothing = ??? + def tail: Nothing = ??? +} + +case class :: [+H, +T <: HList] (hd: H, tl: T) extends HList { + inline override def length <: Int = 1 + tl.length + def head: H = this.hd + def tail: T = this.tl +} + +object Test extends App { + type HNil = HNil.type + val xs = new ::(1, new ::("a", HNil)) + inline val y = xs.length + inline val ise = xs.isEmpty + val hd = xs.head + val tl = xs.tail + val tl2 = xs.tail.tail + + type Concat[+Xs <: HList, +Ys <: HList] <: HList = Xs match { + case HNil => Ys + case x1 :: xs1 => x1 :: Concat[xs1, Ys] + } + + def concat[Xs <: HList, Ys <: HList](xs: Xs, ys: Ys): Concat[Xs, Ys] = { + if xs.isEmpty then ys + else ::(xs.head, concat(xs.tail, ys)) + }.asInstanceOf + + val xs0 = concat(HNil, xs) + val xs1 = concat(xs, HNil) + val xs2 = concat(xs, xs) + val e2: Int = xs2.tail.tail.head +} \ No newline at end of file diff --git a/tests/run/implicitMatch.check b/tests/run/implicitMatch.check index 223b7836fb19..e660fb515087 100644 --- a/tests/run/implicitMatch.check +++ b/tests/run/implicitMatch.check @@ -1 +1,5 @@ +class scala.collection.immutable.TreeSet +class scala.collection.immutable.HashSet +class scala.collection.immutable.TreeSet +class scala.collection.immutable.HashSet B diff --git a/tests/invalid/run/implicitMatch.scala b/tests/run/implicitMatch.scala similarity index 57% rename from tests/invalid/run/implicitMatch.scala rename to tests/run/implicitMatch.scala index e68bec137961..8a9200cca21a 100644 --- a/tests/invalid/run/implicitMatch.scala +++ b/tests/run/implicitMatch.scala @@ -2,11 +2,16 @@ object Test extends App { import collection.immutable.TreeSet import collection.immutable.HashSet - inline def f[T]() = implicit match { + inline def f1[T]() = implicit match { case ord: Ordering[T] => new TreeSet[T] case _ => new HashSet[T] } + inline def f2[T]() = implicit match { + case _: Ordering[T] => new TreeSet[T] + case _ => new HashSet[T] + } + class A class B implicit val b: B = new B @@ -18,8 +23,10 @@ object Test extends App { implicitly[Ordering[String]] - f[String]() - f[AnyRef]() + println(f1[String]().getClass) + println(f1[AnyRef]().getClass) + println(f2[String]().getClass) + println(f2[AnyRef]().getClass) implicitly[B] g diff --git a/tests/run/tuples1.check b/tests/run/tuples1.check index 5fea28566dba..6957f0548302 100644 --- a/tests/run/tuples1.check +++ b/tests/run/tuples1.check @@ -32,5 +32,3 @@ c2_1 = (A,1,1) c2_2 = (A,1,A,1) c2_3 = (A,1,2,A,1) c3_3 = (2,A,1,2,A,1) -(A,1) -> A, (1) -(A,1) -> A, 1, () diff --git a/tests/run/tuples1.scala b/tests/run/tuples1.scala index 1edb3af2c98f..d58371e888c7 100644 --- a/tests/run/tuples1.scala +++ b/tests/run/tuples1.scala @@ -4,6 +4,8 @@ object Test extends App { val x2 = ("A", 1); println(x2) val x3 = 2 *: x2; println(x3) val x4 = "B" *: x3; println(x4) + val y0 = x4(0) + val y1 = x4(1) val x5 = 3 *: x4; println(x5) val x6 = "C" *: x5; println(x6) val x7 = 4 *: x6; println(x7) @@ -34,8 +36,9 @@ object Test extends App { val c2_3 = x2 ++ x3; val c2_3c: (String, Int, Int, String, Int) = c2_3; println(s"c2_3 = $c2_3") val c3_3 = x3 ++ x3; val c3_3c: (Int, String, Int, Int, String, Int) = c3_3; println(s"c3_3 = $c3_3") - inline def decompose1 = x2 match { case x *: xs => (x, xs) } - inline def decompose2 = x2 match { case x *: y *: xs => (x, y, xs) } +/* TODO: re-enable + inline def decompose1 = inline x2 match { case x *: xs => (x, xs) } + inline def decompose2 = inline x2 match { case x *: y *: xs => (x, y, xs) } { val (x, xs) = decompose1 val xc: String = x @@ -49,7 +52,7 @@ object Test extends App { val xsc: Unit = xs println(s"$x2 -> $x, $y, $xs") } - +*/ val x3s: 3 = x3.size val us: 0 = ().size diff --git a/tests/invalid/run/typelevel-defaultValue.scala b/tests/run/typelevel-defaultValue.scala similarity index 91% rename from tests/invalid/run/typelevel-defaultValue.scala rename to tests/run/typelevel-defaultValue.scala index d56b940f5d56..314803beaef1 100644 --- a/tests/invalid/run/typelevel-defaultValue.scala +++ b/tests/run/typelevel-defaultValue.scala @@ -5,7 +5,7 @@ object typelevel { object Test extends App { - inline def defaultValue[T]: Option[Any] = inline typelevel.erasedValue[T] match { + inline def defaultValue[T] <: Option[Any] = inline typelevel.erasedValue[T] match { case _: Byte => Some(0: Byte) case c: Char => Some(0: Char) case d @ (_: Short) => Some(0: Short) diff --git a/tests/run/typelevel-overrides.scala b/tests/run/typelevel-overrides.scala index 2c0fa2631eb9..c8f5f3b5f9df 100644 --- a/tests/run/typelevel-overrides.scala +++ b/tests/run/typelevel-overrides.scala @@ -8,13 +8,13 @@ class A extends T { def f(x: Int) = x } class B extends A { - override inline def f(x: Int) = x match { + override inline def f(x: Int) = inline x match { case 0 => 0 case x => x } } class C extends A with U { - override inline def f(x: Int) = x match { + override inline def f(x: Int) = inline x match { case 0 => 0 case x => x } diff --git a/tests/run/typelevel-peano.check b/tests/run/typelevel-peano.check new file mode 100644 index 000000000000..2e682c75fcc8 --- /dev/null +++ b/tests/run/typelevel-peano.check @@ -0,0 +1,11 @@ +0 +1 +2 +0 +1 +2 +1 +2 +3 +-1 +1 diff --git a/tests/run/typelevel-peano.scala b/tests/run/typelevel-peano.scala new file mode 100644 index 000000000000..c180a6d69f31 --- /dev/null +++ b/tests/run/typelevel-peano.scala @@ -0,0 +1,47 @@ + +import typelevel._ + +object Test extends App { + + inline def toInt[N]: Int = + inline constValue[N] match { + case _: S[n1] => 1 + toInt[n1] + case 0 => 0 + } + + println(toInt[0]) + println(toInt[1]) + println(toInt[2]) + + locally { + inline def toInt[N]: Int = + inline constValueOpt[N] match { + case Some(_: S[n1]) => 1 + toInt[n1] + case Some(0) => 0 + case None => 0 + } + println(toInt[0]) + println(toInt[1]) + println(toInt[2]) + } + + val xs = List(1, 2, 3) + + inline def select(n: Int) = + inline constValueOpt[n.type] match { + case Some(0) => xs(0) + case Some(1) => xs(1) + case Some(2) => xs(2) + case Some(_) => -1 + } + + println(select(0)) + println(select(1)) + println(select(2)) + println(select(3)) + final val idx = 0 + println(select(idx)) +} + + +