From 5b651b7cdf6684f7881e19fa38b536df58d1b96f Mon Sep 17 00:00:00 2001 From: Fengyun Liu Date: Mon, 3 Jul 2023 01:02:38 +0200 Subject: [PATCH 1/9] WIP - initial support for pattern matches --- .../tools/dotc/transform/init/Objects.scala | 106 ++++++++++++++++-- tests/init/neg/patmat.scala | 14 +++ tests/init/pos/patmat.scala | 14 +++ 3 files changed, 123 insertions(+), 11 deletions(-) create mode 100644 tests/init/neg/patmat.scala create mode 100644 tests/init/pos/patmat.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index c76dee520331..d14e050e119b 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -15,6 +15,7 @@ import util.SourcePosition import config.Printers.init as printer import reporting.StoreReporter import reporting.trace as log +import typer.Applications.* import Errors.* import Trace.* @@ -834,11 +835,10 @@ object Objects: /** Handle local variable definition, `val x = e` or `var x = e`. * - * @param ref The value for `this` where the variable is defined. * @param sym The symbol of the variable. * @param value The value of the initializer. */ - def initLocal(ref: Ref, sym: Symbol, value: Value): Contextual[Unit] = log("initialize local " + sym.show + " with " + value.show, printer) { + def initLocal(sym: Symbol, value: Value): Contextual[Unit] = log("initialize local " + sym.show + " with " + value.show, printer) { if sym.is(Flags.Mutable) then val addr = Heap.localVarAddr(summon[Regions.Data], sym, State.currentObject) Env.setLocalVar(sym, addr) @@ -870,9 +870,6 @@ object Objects: case _ => report.warning("[Internal error] Variable not found " + sym.show + "\nenv = " + env.show + ". Calling trace:\n" + Trace.show, Trace.position) Bottom - else if sym.isPatternBound then - // TODO: handle patterns - Cold else given Env.Data = env // Assume forward reference check is doing a good job @@ -1113,11 +1110,9 @@ object Objects: else eval(arg, thisV, klass) - case Match(selector, cases) => - eval(selector, thisV, klass) - // TODO: handle pattern match properly - report.warning("[initChecker] Pattern match is skipped. Trace:\n" + Trace.show, expr) - Bottom + case Match(scrutinee, cases) => + val scrutineeValue = eval(scrutinee, thisV, klass) + patternMatch(scrutineeValue, cases, thisV, klass) case Return(expr, from) => Returns.handle(from.symbol, eval(expr, thisV, klass)) @@ -1151,7 +1146,7 @@ object Objects: // local val definition val rhs = eval(vdef.rhs, thisV, klass) val sym = vdef.symbol - initLocal(thisV.asInstanceOf[Ref], vdef.symbol, rhs) + initLocal(vdef.symbol, rhs) Bottom case ddef : DefDef => @@ -1173,6 +1168,95 @@ object Objects: Bottom } + /** Evaluate the cases against the scrutinee value. + * + * @param scrutinee The abstract value of the scrutinee. + * @param cases The cases to match. + * @param thisV The value for `C.this` where `C` is represented by `klass`. + * @param klass The enclosing class where the type `tp` is located. + */ + def patternMatch(scrutinee: Value, cases: List[CaseDef], thisV: Value, klass: ClassSymbol): Contextual[Value] = + def evalCase(caseDef: CaseDef): Value = + evalPattern(scrutinee, caseDef.pat) + eval(caseDef.guard, thisV, klass) + eval(caseDef.body, thisV, klass) + + /** Abstract evaluation of patterns. + * + * It augments the local environment for bound pattern variables. As symbols are globally + * unique, we can put them in a single environment. + * + * Currently, we assume all cases are reachable, thus all patterns are assumed to match. + */ + def evalPattern(scrutinee: Value, pat: Tree): Value = log("match " + scrutinee.show + " against " + pat.show, printer, (_: Value).show): + pat match + case Alternative(pats) => + for pat <- pats do evalPattern(scrutinee, pat) + scrutinee + + case bind @ Bind(_, pat) => + val value = evalPattern(scrutinee, pat) + initLocal(bind.symbol, value) + scrutinee + + case SeqLiteral(pats, _) => + // TODO: handle unapplySeq + Bottom + + case UnApply(fun, _, pats) => + val fun1 = funPart(fun) + val funRef = fun1.tpe.asInstanceOf[TermRef] + if fun.symbol.name == nme.unapplySeq then + // TODO: handle unapplySeq + () + else + val receiver = evalType(funRef.prefix, thisV, klass) + // TODO: apply implicits + val unapplyRes = call(receiver, funRef.symbol, TraceValue(scrutinee, summon[Trace]) :: Nil, funRef.prefix, superType = NoType, needResolve = true) + // distribute unapply to patterns + val unapplyResTp = funRef.widen.finalResultType + if isProductMatch(unapplyResTp, pats.length) then + // product match + val selectors = productSelectors(unapplyResTp).take(pats.length) + selectors.zip(pats).map { (sel, pat) => + val selectRes = call(unapplyRes, sel, Nil, unapplyResTp, superType = NoType, needResolve = true) + evalPattern(selectRes, pat) + } + else if unapplyResTp <:< defn.BooleanType then + // Boolean extractor, do nothing + () + else + // Get match + val getMember = unapplyResTp.member(nme.get).suchThat(_.info.isParameterless) + // TODO: call isEmpty as well + val getRes = call(unapplyRes, getMember.symbol, Nil, unapplyResTp, superType = NoType, needResolve = true) + if pats.length == 1 then + // single match + evalPattern(getRes, pats.head) + else + val getResTp = getMember.info.widen.finalResultType + val selectors = productSelectors(getResTp).take(pats.length) + selectors.zip(pats).map { (sel, pat) => + val selectRes = call(unapplyRes, sel, Nil, unapplyResTp, superType = NoType, needResolve = true) + evalPattern(selectRes, pat) + } + end if + end if + end if + scrutinee + + case Typed(pat, _) => + evalPattern(scrutinee, pat) + + case tree => + // For all other trees, the semantics is normal. + eval(tree, thisV, klass) + + end evalPattern + + cases.map(evalCase).join + + /** Handle semantics of leaf nodes * * For leaf nodes, their semantics is determined by their types. diff --git a/tests/init/neg/patmat.scala b/tests/init/neg/patmat.scala new file mode 100644 index 000000000000..b38865f96023 --- /dev/null +++ b/tests/init/neg/patmat.scala @@ -0,0 +1,14 @@ +object A: // error + val a: Option[Int] = Some(3) + a match + case Some(x) => println(x * 2 + B.a.size) + case None => println(0) + +object B: + val a = 3 :: 4 :: Nil + a match + case x :: xs => + println(x * 2) + if A.a.isEmpty then println(xs.size) + case Nil => + println(0) diff --git a/tests/init/pos/patmat.scala b/tests/init/pos/patmat.scala new file mode 100644 index 000000000000..72a00f373e75 --- /dev/null +++ b/tests/init/pos/patmat.scala @@ -0,0 +1,14 @@ +object A: + val a: Option[Int] = Some(3) + a match + case Some(x) => println(x * 2) + case None => println(0) + +object B: + val a = 3 :: 4 :: Nil + a match + case x :: xs => + println(x * 2) + println(xs.size) + case Nil => + println(0) From b2a6be15a754fafa7b329a264137c7bd51274239 Mon Sep 17 00:00:00 2001 From: Fengyun Liu Date: Tue, 4 Jul 2023 06:57:00 +0200 Subject: [PATCH 2/9] Complete the support for unapply --- .../tools/dotc/transform/init/Objects.scala | 19 ++++++++++------ .../tools/dotc/transform/init/Util.scala | 3 +++ tests/init/neg/patmat.scala | 22 +++++++++++++++++++ 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index d14e050e119b..c1d31e6ceccd 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -1203,7 +1203,7 @@ object Objects: // TODO: handle unapplySeq Bottom - case UnApply(fun, _, pats) => + case UnApply(fun, implicits, pats) => val fun1 = funPart(fun) val funRef = fun1.tpe.asInstanceOf[TermRef] if fun.symbol.name == nme.unapplySeq then @@ -1211,8 +1211,8 @@ object Objects: () else val receiver = evalType(funRef.prefix, thisV, klass) - // TODO: apply implicits - val unapplyRes = call(receiver, funRef.symbol, TraceValue(scrutinee, summon[Trace]) :: Nil, funRef.prefix, superType = NoType, needResolve = true) + val implicitValues = evalArgs(implicits.map(Arg.apply), thisV, klass) + val unapplyRes = call(receiver, funRef.symbol, TraceValue(scrutinee, summon[Trace]) :: implicitValues, funRef.prefix, superType = NoType, needResolve = true) // distribute unapply to patterns val unapplyResTp = funRef.widen.finalResultType if isProductMatch(unapplyResTp, pats.length) then @@ -1227,14 +1227,16 @@ object Objects: () else // Get match - val getMember = unapplyResTp.member(nme.get).suchThat(_.info.isParameterless) - // TODO: call isEmpty as well - val getRes = call(unapplyRes, getMember.symbol, Nil, unapplyResTp, superType = NoType, needResolve = true) + val isEmptyDenot = unapplyResTp.member(nme.isEmpty).suchThat(_.info.isParameterless) + call(unapplyRes, isEmptyDenot.symbol, Nil, unapplyResTp, superType = NoType, needResolve = true) + + val getDenot = unapplyResTp.member(nme.get).suchThat(_.info.isParameterless) + val getRes = call(unapplyRes, getDenot.symbol, Nil, unapplyResTp, superType = NoType, needResolve = true) if pats.length == 1 then // single match evalPattern(getRes, pats.head) else - val getResTp = getMember.info.widen.finalResultType + val getResTp = getDenot.info.finalResultType val selectors = productSelectors(getResTp).take(pats.length) selectors.zip(pats).map { (sel, pat) => val selectRes = call(unapplyRes, sel, Nil, unapplyResTp, superType = NoType, needResolve = true) @@ -1245,6 +1247,9 @@ object Objects: end if scrutinee + case Ident(nme.WILDCARD) => + scrutinee + case Typed(pat, _) => evalPattern(scrutinee, pat) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Util.scala b/compiler/src/dotty/tools/dotc/transform/init/Util.scala index ad7d2afffbaf..5fcc65bcce10 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Util.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Util.scala @@ -26,6 +26,9 @@ object Util: opaque type Arg = Tree | ByNameArg case class ByNameArg(tree: Tree) + object Arg: + def apply(tree: Tree): Arg = tree + extension (arg: Arg) def isByName = arg.isInstanceOf[ByNameArg] def tree: Tree = arg match diff --git a/tests/init/neg/patmat.scala b/tests/init/neg/patmat.scala index b38865f96023..126e66e7cf7b 100644 --- a/tests/init/neg/patmat.scala +++ b/tests/init/neg/patmat.scala @@ -12,3 +12,25 @@ object B: if A.a.isEmpty then println(xs.size) case Nil => println(0) + +case class Box[T](value: T) +case class Holder[T](value: T) +object C: + (Box(5): Box[Int] | Holder[Int]) match + case Box(x) => x + case Holder(x) => x + + (Box(5): Box[Int] | Holder[Int]) match + case box: Box[Int] => box.value + case holder: Holder[Int] => holder.value + + val a: Int = Inner.b + + object Inner: // error + val b: Int = 10 + + val foo: () => Int = () => C.a + + (Box(foo): Box[() => Int] | Holder[Int]) match + case Box(f) => f() + case Holder(x) => x From d93a21409749e2da27d7f0953249a3cd218098cc Mon Sep 17 00:00:00 2001 From: Fengyun Liu Date: Tue, 4 Jul 2023 07:18:51 +0200 Subject: [PATCH 3/9] Move test to correct directory --- tests/{init => init-global}/neg/patmat.scala | 0 tests/{init => init-global}/pos/patmat.scala | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename tests/{init => init-global}/neg/patmat.scala (100%) rename tests/{init => init-global}/pos/patmat.scala (100%) diff --git a/tests/init/neg/patmat.scala b/tests/init-global/neg/patmat.scala similarity index 100% rename from tests/init/neg/patmat.scala rename to tests/init-global/neg/patmat.scala diff --git a/tests/init/pos/patmat.scala b/tests/init-global/pos/patmat.scala similarity index 100% rename from tests/init/pos/patmat.scala rename to tests/init-global/pos/patmat.scala From 6d645b70a37c7f06e13cd26a2dc6aea2064bb685 Mon Sep 17 00:00:00 2001 From: Fengyun Liu Date: Mon, 10 Jul 2023 08:20:49 +0200 Subject: [PATCH 4/9] Add support for unapplySeq --- .../tools/dotc/transform/init/Objects.scala | 107 ++++++++++++++++-- 1 file changed, 98 insertions(+), 9 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index c1d31e6ceccd..617a667d40df 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -6,12 +6,14 @@ import core.* import Contexts.* import Symbols.* import Types.* +import Denotations.Denotation import StdNames.* +import Names.TermName import NameKinds.OuterSelectName import NameKinds.SuperAccessorName import ast.tpd.* -import util.SourcePosition +import util.{ SourcePosition, NoSourcePosition } import config.Printers.init as printer import reporting.StoreReporter import reporting.trace as log @@ -1176,6 +1178,16 @@ object Objects: * @param klass The enclosing class where the type `tp` is located. */ def patternMatch(scrutinee: Value, cases: List[CaseDef], thisV: Value, klass: ClassSymbol): Contextual[Value] = + // expected member types for `unapplySeq` + def lengthType = ExprType(defn.IntType) + def lengthCompareType = MethodType(List(defn.IntType), defn.IntType) + def applyType(elemTp: Type) = MethodType(List(defn.IntType), elemTp) + def dropType(elemTp: Type) = MethodType(List(defn.IntType), defn.CollectionSeqType.appliedTo(elemTp)) + def toSeqType(elemTp: Type) = ExprType(defn.CollectionSeqType.appliedTo(elemTp)) + + def getMemberMethod(receiver: Type, name: TermName, tp: Type): Denotation = + receiver.member(name).suchThat(receiver.memberInfo(_) <:< tp) + def evalCase(caseDef: CaseDef): Value = evalPattern(scrutinee, caseDef.pat) eval(caseDef.guard, thisV, klass) @@ -1206,18 +1218,59 @@ object Objects: case UnApply(fun, implicits, pats) => val fun1 = funPart(fun) val funRef = fun1.tpe.asInstanceOf[TermRef] + val unapplyResTp = funRef.widen.finalResultType + + val receiver = evalType(funRef.prefix, thisV, klass) + val implicitValues = evalArgs(implicits.map(Arg.apply), thisV, klass) + // TODO: implicit values may appear before and/or after the scrutinee parameter. + val unapplyRes = call(receiver, funRef.symbol, TraceValue(scrutinee, summon[Trace]) :: implicitValues, funRef.prefix, superType = NoType, needResolve = true) + if fun.symbol.name == nme.unapplySeq then - // TODO: handle unapplySeq - () + var resultTp = unapplyResTp + var elemTp = unapplySeqTypeElemTp(resultTp) + var arity = productArity(resultTp, NoSourcePosition) + var needsGet = false + if (!elemTp.exists && arity <= 0) { + needsGet = true + resultTp = resultTp.select(nme.get).finalResultType + elemTp = unapplySeqTypeElemTp(resultTp.widen) + arity = productSelectorTypes(resultTp, NoSourcePosition).size + } + + var resToMatch = unapplyRes + + if needsGet then + // Get match + val isEmptyDenot = unapplyResTp.member(nme.isEmpty).suchThat(_.info.isParameterless) + call(unapplyRes, isEmptyDenot.symbol, Nil, unapplyResTp, superType = NoType, needResolve = true) + + val getDenot = unapplyResTp.member(nme.get).suchThat(_.info.isParameterless) + resToMatch = call(unapplyRes, getDenot.symbol, Nil, unapplyResTp, superType = NoType, needResolve = true) + end if + + if elemTp.exists then + // sequence match + evalSeqPatterns(resToMatch, resultTp, elemTp, pats) + else + // product sequence match + val selectors = productSelectors(resultTp) + assert(selectors.length <= pats.length) + selectors.init.zip(pats).map { (sel, pat) => + val selectRes = call(resToMatch, sel, Nil, resultTp, superType = NoType, needResolve = true) + evalPattern(selectRes, pat) + } + val seqPats = pats.drop(selectors.length - 1) + val toSeqRes = call(resToMatch, selectors.last, Nil, resultTp, superType = NoType, needResolve = true) + val toSeqResTp = resultTp.memberInfo(selectors.last).finalResultType + evalSeqPatterns(toSeqRes, toSeqResTp, elemTp, seqPats) + end if + else - val receiver = evalType(funRef.prefix, thisV, klass) - val implicitValues = evalArgs(implicits.map(Arg.apply), thisV, klass) - val unapplyRes = call(receiver, funRef.symbol, TraceValue(scrutinee, summon[Trace]) :: implicitValues, funRef.prefix, superType = NoType, needResolve = true) // distribute unapply to patterns - val unapplyResTp = funRef.widen.finalResultType if isProductMatch(unapplyResTp, pats.length) then // product match - val selectors = productSelectors(unapplyResTp).take(pats.length) + val selectors = productSelectors(unapplyResTp) + assert(selectors.length == pats.length) selectors.zip(pats).map { (sel, pat) => val selectRes = call(unapplyRes, sel, Nil, unapplyResTp, superType = NoType, needResolve = true) evalPattern(selectRes, pat) @@ -1239,7 +1292,7 @@ object Objects: val getResTp = getDenot.info.finalResultType val selectors = productSelectors(getResTp).take(pats.length) selectors.zip(pats).map { (sel, pat) => - val selectRes = call(unapplyRes, sel, Nil, unapplyResTp, superType = NoType, needResolve = true) + val selectRes = call(unapplyRes, sel, Nil, getResTp, superType = NoType, needResolve = true) evalPattern(selectRes, pat) } end if @@ -1259,6 +1312,42 @@ object Objects: end evalPattern + /** + * Evaluate a sequence value against sequence patterns. + */ + def evalSeqPatterns(scrutinee: Value, scrutineeType: Type, elemType: Type, pats: List[Tree]): Unit = + // call .lengthCompare or .length + val lengthCompareDenot = getMemberMethod(scrutineeType, nme.lengthCompare, lengthCompareType) + if lengthCompareDenot.exists then + call(scrutinee, lengthCompareDenot.symbol, TraceValue(Bottom, summon[Trace]) :: Nil, scrutineeType, superType = NoType, needResolve = true) + else + val lengthDenot = getMemberMethod(scrutineeType, nme.length, lengthType) + call(scrutinee, lengthDenot.symbol, Nil, scrutineeType, superType = NoType, needResolve = true) + end if + + // call .apply + val applyDenot = getMemberMethod(scrutineeType, nme.apply, applyType(elemType)) + val applyRes = call(scrutinee, applyDenot.symbol, TraceValue(Bottom, summon[Trace]) :: Nil, scrutineeType, superType = NoType, needResolve = true) + + if isWildcardStarArg(pats.last) then + if pats.size == 1 then + // call .toSeq + val toSeqDenot = scrutineeType.member(nme.toSeq).suchThat(_.info.isParameterless) + val toSeqRes = call(scrutinee, toSeqDenot.symbol, Nil, scrutineeType, superType = NoType, needResolve = true) + evalPattern(toSeqRes, pats.head) + else + // call .drop + val dropDenot = getMemberMethod(scrutineeType, nme.drop, applyType(elemType)) + val dropRes = call(scrutinee, dropDenot.symbol, TraceValue(Bottom, summon[Trace]) :: Nil, scrutineeType, superType = NoType, needResolve = true) + for pat <- pats.init do evalPattern(applyRes, pat) + evalPattern(dropRes, pats.last) + end if + else + // no patterns like `xs*` + for pat <- pats do evalPattern(applyRes, pat) + end evalSeqPatterns + + cases.map(evalCase).join From 59e22a291a8053d67ea4c15a108b0541c06cebb0 Mon Sep 17 00:00:00 2001 From: Fengyun Liu Date: Mon, 10 Jul 2023 09:08:20 +0200 Subject: [PATCH 5/9] Add unapplySeq tests --- .../tools/dotc/transform/init/Objects.scala | 11 +++++------ tests/init-global/neg/patmat-unapplySeq.check | 11 +++++++++++ tests/init-global/neg/patmat-unapplySeq.scala | 17 +++++++++++++++++ tests/init-global/neg/patmat-unapplySeq2.scala | 17 +++++++++++++++++ 4 files changed, 50 insertions(+), 6 deletions(-) create mode 100644 tests/init-global/neg/patmat-unapplySeq.check create mode 100644 tests/init-global/neg/patmat-unapplySeq.scala create mode 100644 tests/init-global/neg/patmat-unapplySeq2.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index 617a667d40df..8c7a63b76f0d 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -1201,6 +1201,7 @@ object Objects: * Currently, we assume all cases are reachable, thus all patterns are assumed to match. */ def evalPattern(scrutinee: Value, pat: Tree): Value = log("match " + scrutinee.show + " against " + pat.show, printer, (_: Value).show): + val trace2 = Trace.trace.add(pat) pat match case Alternative(pats) => for pat <- pats do evalPattern(scrutinee, pat) @@ -1211,11 +1212,9 @@ object Objects: initLocal(bind.symbol, value) scrutinee - case SeqLiteral(pats, _) => - // TODO: handle unapplySeq - Bottom - case UnApply(fun, implicits, pats) => + given Trace = trace2 + val fun1 = funPart(fun) val funRef = fun1.tpe.asInstanceOf[TermRef] val unapplyResTp = funRef.widen.finalResultType @@ -1300,7 +1299,7 @@ object Objects: end if scrutinee - case Ident(nme.WILDCARD) => + case Ident(nme.WILDCARD) | Ident(nme.WILDCARD_STAR) => scrutinee case Typed(pat, _) => @@ -1315,7 +1314,7 @@ object Objects: /** * Evaluate a sequence value against sequence patterns. */ - def evalSeqPatterns(scrutinee: Value, scrutineeType: Type, elemType: Type, pats: List[Tree]): Unit = + def evalSeqPatterns(scrutinee: Value, scrutineeType: Type, elemType: Type, pats: List[Tree])(using Trace): Unit = // call .lengthCompare or .length val lengthCompareDenot = getMemberMethod(scrutineeType, nme.lengthCompare, lengthCompareType) if lengthCompareDenot.exists then diff --git a/tests/init-global/neg/patmat-unapplySeq.check b/tests/init-global/neg/patmat-unapplySeq.check new file mode 100644 index 000000000000..74093b029614 --- /dev/null +++ b/tests/init-global/neg/patmat-unapplySeq.check @@ -0,0 +1,11 @@ +-- Error: tests/init-global/neg/patmat-unapplySeq.scala:8:32 ----------------------------------------------------------- +8 | def apply(i: Int): Box = array(i) // error + | ^^^^^^^^ + |Reading mutable state of object A during initialization of object B. + |Reading mutable state of other static objects is forbidden as it breaks initialization-time irrelevance. Calling trace: + |-> object B: [ patmat-unapplySeq.scala:15 ] + | ^ + |-> case A(b) => [ patmat-unapplySeq.scala:17 ] + | ^^^^ + |-> def apply(i: Int): Box = array(i) // error [ patmat-unapplySeq.scala:8 ] + | ^^^^^^^^ diff --git a/tests/init-global/neg/patmat-unapplySeq.scala b/tests/init-global/neg/patmat-unapplySeq.scala new file mode 100644 index 000000000000..81c853a6e19f --- /dev/null +++ b/tests/init-global/neg/patmat-unapplySeq.scala @@ -0,0 +1,17 @@ +object A: + class Box(var x: Int) + + val array: Array[Box] = new Array(1) + array(0) = new Box(10) + + def length: Int = array.length + def apply(i: Int): Box = array(i) // error + def drop(n: Int): Seq[Box] = array.toSeq + def toSeq: Seq[Box] = array.toSeq + + def unapplySeq(array: Array[Box]): A.type = this + + +object B: + A.array match + case A(b) => diff --git a/tests/init-global/neg/patmat-unapplySeq2.scala b/tests/init-global/neg/patmat-unapplySeq2.scala new file mode 100644 index 000000000000..adab9495db49 --- /dev/null +++ b/tests/init-global/neg/patmat-unapplySeq2.scala @@ -0,0 +1,17 @@ +object A: + class Box(var x: Int) + + val array: Array[Box] = new Array(1) + array(0) = new Box(10) + + def length: Int = array.length + def apply(i: Int): Box = array(i) // error + def drop(n: Int): Seq[Box] = array.toSeq + def toSeq: Seq[Box] = array.toSeq + + def unapplySeq(array: Array[Box]): A.type = this + + +object B: + A.array match + case A(b*) => From 29a350f8147bf84cbe1e78bac4bacbcc63e88930 Mon Sep 17 00:00:00 2001 From: Fengyun Liu Date: Fri, 14 Jul 2023 00:37:20 +0200 Subject: [PATCH 6/9] Address review --- compiler/src/dotty/tools/dotc/transform/init/Objects.scala | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index 8c7a63b76f0d..bb3e6df503ec 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -1171,6 +1171,11 @@ object Objects: } /** Evaluate the cases against the scrutinee value. + * + * It returns the scrutinee in most cases. The main effect of the function is for its side effects of adding bindings + * to the environment. + * + * See https://docs.scala-lang.org/scala3/reference/changed-features/pattern-matching.html * * @param scrutinee The abstract value of the scrutinee. * @param cases The cases to match. @@ -1348,7 +1353,7 @@ object Objects: cases.map(evalCase).join - + end patternMatch /** Handle semantics of leaf nodes * From 7406ddbb96ea1d61f764ea54a0b2858cdbe06fb8 Mon Sep 17 00:00:00 2001 From: Fengyun Liu Date: Fri, 14 Jul 2023 01:04:46 +0200 Subject: [PATCH 7/9] Fix crash --- .../src/dotty/tools/dotc/transform/init/Objects.scala | 9 +++++++-- tests/init-global/pos/patmat-interpolator.scala | 3 +++ 2 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 tests/init-global/pos/patmat-interpolator.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index bb3e6df503ec..94b6e3f6e91e 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -1224,7 +1224,12 @@ object Objects: val funRef = fun1.tpe.asInstanceOf[TermRef] val unapplyResTp = funRef.widen.finalResultType - val receiver = evalType(funRef.prefix, thisV, klass) + val receiver = fun1 match + case ident: Ident => + evalType(funRef.prefix, thisV, klass) + case select: Select => + eval(select.qualifier, thisV, klass) + val implicitValues = evalArgs(implicits.map(Arg.apply), thisV, klass) // TODO: implicit values may appear before and/or after the scrutinee parameter. val unapplyRes = call(receiver, funRef.symbol, TraceValue(scrutinee, summon[Trace]) :: implicitValues, funRef.prefix, superType = NoType, needResolve = true) @@ -1413,7 +1418,7 @@ object Objects: resolveThis(tref.classSymbol.asClass, thisV, klass) case _ => - throw new Exception("unexpected type: " + tp) + throw new Exception("unexpected type: " + tp + ", Trace:\n" + Trace.show) } /** Evaluate arguments of methods and constructors */ diff --git a/tests/init-global/pos/patmat-interpolator.scala b/tests/init-global/pos/patmat-interpolator.scala new file mode 100644 index 000000000000..2df74326b77a --- /dev/null +++ b/tests/init-global/pos/patmat-interpolator.scala @@ -0,0 +1,3 @@ +object Test: + val RootPackage = "_root_/" + val s"${RootPackageName @ _}/" = RootPackage: @unchecked From a767c6bc7ca77088939d3a10431d1d4b4bcbdf1c Mon Sep 17 00:00:00 2001 From: Fengyun Liu Date: Fri, 14 Jul 2023 11:01:07 +0200 Subject: [PATCH 8/9] Fix swallowing of warnings If the span of an error message is too long (e.g., for a huge object definition), some warnings will be swallowed. --- compiler/src/dotty/tools/dotc/transform/init/Objects.scala | 2 +- compiler/src/dotty/tools/dotc/transform/init/Trace.scala | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index 94b6e3f6e91e..9d6c3020d406 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -252,7 +252,7 @@ object Objects: val joinedTrace = data.pendingTraces.slice(index + 1, data.checkingObjects.size).foldLeft(pendingTrace) { (a, acc) => acc ++ a } val callTrace = Trace.buildStacktrace(joinedTrace, "Calling trace:\n") val cycle = data.checkingObjects.slice(index, data.checkingObjects.size) - val pos = clazz.defTree + val pos = clazz.defTree.sourcePos.focus report.warning("Cyclic initialization: " + cycle.map(_.klass.show).mkString(" -> ") + " -> " + clazz.show + ". " + callTrace, pos) end if data.checkingObjects(index) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Trace.scala b/compiler/src/dotty/tools/dotc/transform/init/Trace.scala index 7dfbc0b6cfa5..7f3208dae952 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Trace.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Trace.scala @@ -49,7 +49,12 @@ object Trace: val code = SyntaxHighlighting.highlight(pos.lineContent.trim.nn) i"$code\t$loc" else - tree.show + tree match + case defDef: DefTree => + // The definition can be huge, avoid printing the whole definition. + defDef.symbol.show + case _ => + tree.show val positionMarkerLine = if pos.exists && pos.source.exists then positionMarker(pos) From 4cfcacf5515be9d0bfff2cdcf8878dcd4c51cb5c Mon Sep 17 00:00:00 2001 From: Fengyun Liu Date: Fri, 14 Jul 2023 12:03:37 +0200 Subject: [PATCH 9/9] Fix tests after error reporting change --- tests/init-global/neg/global-cycle1.check | 22 ++++++++++------------ tests/init-global/neg/global-cycle6.scala | 2 +- tests/init-global/neg/t9115.scala | 2 +- tests/init-global/neg/t9312.scala | 2 +- tests/init-global/neg/t9360.scala | 2 +- 5 files changed, 14 insertions(+), 16 deletions(-) diff --git a/tests/init-global/neg/global-cycle1.check b/tests/init-global/neg/global-cycle1.check index e0125630c077..faed741761a7 100644 --- a/tests/init-global/neg/global-cycle1.check +++ b/tests/init-global/neg/global-cycle1.check @@ -1,17 +1,15 @@ -- Error: tests/init-global/neg/global-cycle1.scala:1:7 ---------------------------------------------------------------- 1 |object A { // error - |^ - |Cyclic initialization: object A -> object B -> object A. Calling trace: - |-> object A { // error [ global-cycle1.scala:1 ] - | ^ - |-> val a: Int = B.b [ global-cycle1.scala:2 ] - | ^ - |-> object B { [ global-cycle1.scala:5 ] - | ^ - |-> val b: Int = A.a // error [ global-cycle1.scala:6 ] - | ^ -2 | val a: Int = B.b -3 |} + | ^ + | Cyclic initialization: object A -> object B -> object A. Calling trace: + | -> object A { // error [ global-cycle1.scala:1 ] + | ^ + | -> val a: Int = B.b [ global-cycle1.scala:2 ] + | ^ + | -> object B { [ global-cycle1.scala:5 ] + | ^ + | -> val b: Int = A.a // error [ global-cycle1.scala:6 ] + | ^ -- Error: tests/init-global/neg/global-cycle1.scala:6:17 --------------------------------------------------------------- 6 | val b: Int = A.a // error | ^^^ diff --git a/tests/init-global/neg/global-cycle6.scala b/tests/init-global/neg/global-cycle6.scala index 2d4f23c25187..36e3ab0b6a94 100644 --- a/tests/init-global/neg/global-cycle6.scala +++ b/tests/init-global/neg/global-cycle6.scala @@ -1,7 +1,7 @@ object A { // error val n: Int = B.m class Inner { - println(n) + println(n) // error } } diff --git a/tests/init-global/neg/t9115.scala b/tests/init-global/neg/t9115.scala index e7cfe09e560c..a3020c6939a8 100644 --- a/tests/init-global/neg/t9115.scala +++ b/tests/init-global/neg/t9115.scala @@ -1,4 +1,4 @@ -object D { +object D { // error def aaa = 1 //that’s the reason class Z (depends: Any) case object D1 extends Z(aaa) // 'null' when calling D.D1 first time // error diff --git a/tests/init-global/neg/t9312.scala b/tests/init-global/neg/t9312.scala index 703cf67e05c4..d88093a2f67a 100644 --- a/tests/init-global/neg/t9312.scala +++ b/tests/init-global/neg/t9312.scala @@ -8,7 +8,7 @@ object DeadLockTest { } - object Parent { + object Parent { // error trait Child { Thread.sleep(2000) // ensure concurrent behavior val parent = Parent diff --git a/tests/init-global/neg/t9360.scala b/tests/init-global/neg/t9360.scala index 291c4dd05db1..2ec0c740d739 100644 --- a/tests/init-global/neg/t9360.scala +++ b/tests/init-global/neg/t9360.scala @@ -2,7 +2,7 @@ class BaseClass(s: String) { def print: Unit = () } -object Obj { +object Obj { // error val s: String = "hello" object AObj extends BaseClass(s) // error