diff --git a/compiler/src/dotty/tools/dotc/transform/init/Errors.scala b/compiler/src/dotty/tools/dotc/transform/init/Errors.scala index bd89201cf738..38d2263b54c3 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Errors.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Errors.scala @@ -6,99 +6,111 @@ package init import ast.tpd._ import core._ import util.SourcePosition +import util.Property import Decorators._, printing.SyntaxHighlighting import Types._, Symbols._, Contexts._ import scala.collection.mutable object Errors: + private val IsFromPromotion = new Property.Key[Boolean] + sealed trait Error: def trace: Seq[Tree] def show(using Context): String def pos(using Context): SourcePosition = trace.last.sourcePos + def stacktrace(using Context): String = + val preamble: String = + if ctx.property(IsFromPromotion).nonEmpty + then " Promotion trace:\n" + else " Calling trace:\n" + buildStacktrace(trace, preamble) + def issue(using Context): Unit = report.warning(show, this.pos) + end Error + + def buildStacktrace(trace: Seq[Tree], preamble: String)(using Context): String = if trace.isEmpty then "" else preamble + { + var lastLineNum = -1 + var lines: mutable.ArrayBuffer[String] = new mutable.ArrayBuffer + trace.foreach { tree => + val pos = tree.sourcePos + val prefix = "-> " + val line = + if pos.source.exists then + val loc = "[ " + pos.source.file.name + ":" + (pos.line + 1) + " ]" + val code = SyntaxHighlighting.highlight(pos.lineContent.trim.nn) + i"$code\t$loc" + else + tree.show + val positionMarkerLine = + if pos.exists && pos.source.exists then + positionMarker(pos) + else "" + + // always use the more precise trace location + if lastLineNum == pos.line then + lines.dropRightInPlace(1) - def stacktrace(preamble: String = " Calling trace:\n")(using Context): String = if trace.isEmpty then "" else preamble + { - var lastLineNum = -1 - var lines: mutable.ArrayBuffer[String] = new mutable.ArrayBuffer - trace.foreach { tree => - val pos = tree.sourcePos - val prefix = "-> " - val line = - if pos.source.exists then - val loc = "[ " + pos.source.file.name + ":" + (pos.line + 1) + " ]" - val code = SyntaxHighlighting.highlight(pos.lineContent.trim.nn) - i"$code\t$loc" - else - tree.show - val positionMarkerLine = - if pos.exists && pos.source.exists then - positionMarker(pos) - else "" - - // always use the more precise trace location - if lastLineNum == pos.line then - lines.dropRightInPlace(1) - - lines += (prefix + line + "\n" + positionMarkerLine) - - lastLineNum = pos.line - } - val sb = new StringBuilder - for line <- lines do sb.append(line) - sb.toString + lines += (prefix + line + "\n" + positionMarkerLine) + + lastLineNum = pos.line } + val sb = new StringBuilder + for line <- lines do sb.append(line) + sb.toString + } - /** Used to underline source positions in the stack trace - * pos.source must exist - */ - private def positionMarker(pos: SourcePosition): String = - val trimmed = pos.lineContent.takeWhile(c => c.isWhitespace).length - val padding = pos.startColumnPadding.substring(trimmed).nn + " " - val carets = - if (pos.startLine == pos.endLine) - "^" * math.max(1, pos.endColumn - pos.startColumn) - else "^" + /** Used to underline source positions in the stack trace + * pos.source must exist + */ + private def positionMarker(pos: SourcePosition): String = + val trimmed = pos.lineContent.takeWhile(c => c.isWhitespace).length + val padding = pos.startColumnPadding.substring(trimmed).nn + " " + val carets = + if (pos.startLine == pos.endLine) + "^" * math.max(1, pos.endColumn - pos.startColumn) + else "^" - s"$padding$carets\n" + s"$padding$carets\n" - override def toString() = this.getClass.getName.nn - end Error + override def toString() = this.getClass.getName.nn /** Access non-initialized field */ case class AccessNonInit(field: Symbol, trace: Seq[Tree]) extends Error: def source: Tree = trace.last def show(using Context): String = - "Access non-initialized " + field.show + "." + stacktrace() + "Access non-initialized " + field.show + "." + stacktrace override def pos(using Context): SourcePosition = field.sourcePos /** Promote a value under initialization to fully-initialized */ case class PromoteError(msg: String, trace: Seq[Tree]) extends Error: - def show(using Context): String = msg + stacktrace() + def show(using Context): String = msg + stacktrace case class AccessCold(field: Symbol, trace: Seq[Tree]) extends Error: def show(using Context): String = - "Access field " + field.show + " on a cold object." + stacktrace() + "Access field " + field.show + " on a cold object." + stacktrace case class CallCold(meth: Symbol, trace: Seq[Tree]) extends Error: def show(using Context): String = - "Call method " + meth.show + " on a cold object." + stacktrace() + "Call method " + meth.show + " on a cold object." + stacktrace case class CallUnknown(meth: Symbol, trace: Seq[Tree]) extends Error: def show(using Context): String = val prefix = if meth.is(Flags.Method) then "Calling the external method " else "Accessing the external field" - prefix + meth.show + " may cause initialization errors." + stacktrace() + prefix + meth.show + " may cause initialization errors." + stacktrace /** Promote a value under initialization to fully-initialized */ case class UnsafePromotion(msg: String, trace: Seq[Tree], error: Error) extends Error: def show(using Context): String = - msg + stacktrace() + "\n" + - "Promoting the value to fully initialized failed due to the following problem:\n" + - error.show + msg + stacktrace + "\n" + + "Promoting the value to hot failed due to the following problem:\n" + { + val ctx2 = ctx.withProperty(IsFromPromotion, Some(true)) + error.show(using ctx2) + } /** Unsafe leaking a non-hot value as constructor arguments * @@ -106,7 +118,7 @@ object Errors: */ case class UnsafeLeaking(trace: Seq[Tree], error: Error, nonHotOuterClass: Symbol, argsIndices: List[Int]) extends Error: def show(using Context): String = - "Problematic object instantiation: " + argumentInfo() + stacktrace() + "\n" + + "Problematic object instantiation: " + argumentInfo() + stacktrace + "\n" + "It leads to the following error during object initialization:\n" + error.show @@ -129,5 +141,5 @@ object Errors: acc + text2 } val verb = if multiple then " are " else " is " - val adjective = "not fully initialized." + val adjective = "not hot." subject + verb + adjective diff --git a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala index 8cf8857bf04c..4765066d571f 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -479,6 +479,9 @@ object Semantic: def add(node: Tree): Trace = trace :+ node def toVector: Vector[Tree] = trace + def show(using trace: Trace, ctx: Context): String = buildStacktrace(trace, "\n") + + def position(using trace: Trace): Tree = trace.last type Trace = Trace.Trace import Trace.* @@ -652,7 +655,7 @@ object Semantic: value.promote(msg) value - def select(field: Symbol, needResolve: Boolean = true): Contextual[Value] = log("select " + field.show + ", this = " + value, printer, (_: Value).show) { + def select(field: Symbol, receiver: Type, needResolve: Boolean = true): Contextual[Value] = log("select " + field.show + ", this = " + value, printer, (_: Value).show) { if promoted.isCurrentObjectPromoted then Hot else value match { case Hot => @@ -668,7 +671,7 @@ object Semantic: if target.is(Flags.Lazy) then val rhs = target.defTree.asInstanceOf[ValDef].rhs eval(rhs, ref, target.owner.asClass, cacheResult = true) - else + else if target.exists then val obj = ref.objekt if obj.hasField(target) then obj.field(target) @@ -691,13 +694,21 @@ object Semantic: val error = AccessNonInit(target, trace.toVector) reporter.report(error) Hot + else + if ref.klass.isSubClass(receiver.widenSingleton.classSymbol) then + report.error("[Internal error] Unexpected resolution failure: ref.klass = " + ref.klass.show + ", field = " + field.show + Trace.show, Trace.position) + Hot + else + // This is possible due to incorrect type cast. + // See tests/init/pos/Type.scala + Hot case fun: Fun => - report.error("[Internal error] unexpected tree in selecting a function, fun = " + fun.expr.show, fun.expr) + report.error("[Internal error] unexpected tree in selecting a function, fun = " + fun.expr.show + Trace.show, fun.expr) Hot case RefSet(refs) => - refs.map(_.select(field)).join + refs.map(_.select(field, receiver)).join } } @@ -809,13 +820,21 @@ object Semantic: val error = CallUnknown(target, trace.toVector) reporter.report(error) Hot - else + else if target.exists then // method call resolves to a field val obj = ref.objekt if obj.hasField(target) then obj.field(target) else - value.select(target, needResolve = false) + value.select(target, receiver, needResolve = false) + else + if ref.klass.isSubClass(receiver.widenSingleton.classSymbol) then + report.error("Unexpected resolution failure: ref.klass = " + ref.klass.show + ", meth = " + meth.show + Trace.show, Trace.position) + Hot + else + // This is possible due to incorrect type cast. + // See tests/init/pos/Type.scala + Hot case Fun(body, thisV, klass) => // meth == NoSymbol for poly functions @@ -840,7 +859,7 @@ object Semantic: value match { case Hot | Cold | _: RefSet | _: Fun => - report.error("unexpected constructor call, meth = " + ctor + ", value = " + value, trace.toVector.last) + report.error("[Internal error] unexpected constructor call, meth = " + ctor + ", value = " + value + Trace.show, Trace.position) Hot case ref: Warm if ref.isPopulatingParams => @@ -947,7 +966,7 @@ object Semantic: warm case Fun(body, thisV, klass) => - report.error("[Internal error] unexpected tree in instantiating a function, fun = " + body.show, trace.toVector.last) + report.error("[Internal error] unexpected tree in instantiating a function, fun = " + body.show + Trace.show, Trace.position) Hot case RefSet(refs) => @@ -967,7 +986,7 @@ object Semantic: case Hot => Hot case ref: Ref => ref.objekt.field(sym) case _ => - report.error("[Internal error] unexpected this value accessing local variable, sym = " + sym.show + ", thisValue = " + thisValue2.show, trace.toVector.last) + report.error("[Internal error] unexpected this value accessing local variable, sym = " + sym.show + ", thisValue = " + thisValue2.show + Trace.show, Trace.position) Hot else if sym.is(Flags.Param) then Hot @@ -985,7 +1004,7 @@ object Semantic: case ref: Ref => eval(vdef.rhs, ref, enclosingClass) case _ => - report.error("[Internal error] unexpected this value when accessing local variable, sym = " + sym.show + ", thisValue = " + thisValue2.show, trace.toVector.last) + report.error("[Internal error] unexpected this value when accessing local variable, sym = " + sym.show + ", thisValue = " + thisValue2.show + Trace.show, Trace.position) Hot end match @@ -1081,7 +1100,7 @@ object Semantic: eval(body, thisV, klass) } given Trace = Trace.empty.add(body) - res.promote("The function return value is not fully initialized. Found = " + res.show + ". ") + res.promote("The function return value is not hot. Found = " + res.show + ".") } if errors.nonEmpty then reporter.report(UnsafePromotion(msg, trace.toVector, errors.head)) @@ -1107,33 +1126,64 @@ object Semantic: * If the object contains nested classes as members, the checker simply * reports a warning to avoid expensive checks. * - * TODO: we need to revisit whether this is needed once we make the - * system more flexible in other dimentions: e.g. leak to - * methods or constructors, or use ownership for creating cold data structures. */ def tryPromote(msg: String): Contextual[List[Error]] = log("promote " + warm.show + ", promoted = " + promoted, printer) { val classRef = warm.klass.appliedRef val hasInnerClass = classRef.memberClasses.filter(_.symbol.hasSource).nonEmpty if hasInnerClass then - return PromoteError(msg + "Promotion cancelled as the value contains inner classes. ", trace.toVector) :: Nil + return PromoteError(msg + "Promotion cancelled as the value contains inner classes.", trace.toVector) :: Nil + + val obj = warm.objekt + + def doPromote(klass: ClassSymbol, subClass: ClassSymbol, subClassSegmentHot: Boolean)(using Reporter): Unit = + val outer = obj.outer(klass) + val isHotSegment = outer.isHot && { + val ctor = klass.primaryConstructor + val ctorDef = ctor.defTree.asInstanceOf[DefDef] + val params = ctorDef.termParamss.flatten.map(_.symbol) + // We have cached all parameters on the object + params.forall(param => obj.field(param).isHot) + } - val errors = Reporter.stopEarly { - for klass <- warm.klass.baseClasses if klass.hasSource do + // check invariant: subClassSegmentHot => isHotSegment + if subClassSegmentHot && !isHotSegment then + report.error("[Internal error] Expect current segment to hot in promotion, current klass = " + klass.show + + ", subclass = " + subClass.show + Trace.show, Trace.position) + + // If the outer and parameters of a class are all hot, then accessing fields and methods of the current + // segment of the object should be OK. They may only create problems via virtual method calls on `this`, but + // those methods are checked as part of the check for the class where they are defined. + if !isHotSegment then for member <- klass.info.decls do if !member.isType && !member.isConstructor && member.hasSource && !member.is(Flags.Deferred) then + given Trace = Trace.empty if member.is(Flags.Method, butNot = Flags.Accessor) then - withTrace(Trace.empty) { - val args = member.info.paramInfoss.flatten.map(_ => ArgInfo(Hot, Trace.empty)) - val res = warm.call(member, args, receiver = NoType, superType = NoType) - res.promote("Cannot prove that the return value of " + member.show + " is fully initialized. Found = " + res.show + ". ") + val args = member.info.paramInfoss.flatten.map(_ => ArgInfo(Hot, Trace.empty)) + val res = warm.call(member, args, receiver = warm.klass.typeRef, superType = NoType) + withTrace(trace.add(member.defTree)) { + res.promote("Cannot prove that the return value of " + member.show + " is hot. Found = " + res.show + ".") } else - withTrace(Trace.empty) { - val res = warm.select(member) - res.promote("Cannot prove that the field " + member.show + " is fully initialized. Found = " + res.show + ". ") + val res = warm.select(member, receiver = warm.klass.typeRef) + withTrace(trace.add(member.defTree)) { + res.promote("Cannot prove that the field " + member.show + " is hot. Found = " + res.show + ".") } end for - end for + + // Promote parents + // + // Note that a parameterized trait may only get parameters from the class that extends the trait. + // A trait may not supply constructor arguments to another trait. + if !klass.is(Flags.Trait) then + for parent <- klass.parentSyms if parent.hasSource do doPromote(parent.asClass, klass, isHotSegment) + // We still need to handle indirectly extended traits via traits, which are not in the parent list. + val superCls = klass.superClass + val mixins = klass.baseClasses.tail.takeWhile(_ != superCls) + for mixin <- mixins if mixin.hasSource do doPromote(mixin.asClass, klass, isHotSegment) + end doPromote + + val errors = Reporter.stopEarly { + doPromote(warm.klass, subClass = warm.klass, subClassSegmentHot = false) } if errors.isEmpty then Nil @@ -1231,7 +1281,7 @@ object Semantic: /** Utility definition used for better error-reporting of argument errors */ case class ArgInfo(value: Value, trace: Trace): def promote: Contextual[Unit] = withTrace(trace) { - value.promote("Cannot prove the argument is fully initialized. Only fully initialized values are safe to leak.\nFound = " + value.show + ". ") + value.promote("Cannot prove the method argument is hot. Only hot values are safe to leak.\nFound = " + value.show + ".") } /** Evaluate an expression with the given value for `this` in a given class `klass` @@ -1344,7 +1394,7 @@ object Semantic: resolveThis(target, qual, current.asClass) } case _ => - withTrace(trace2) { qual.select(expr.symbol) } + withTrace(trace2) { qual.select(expr.symbol, receiver = qualifier.tpe) } case _: This => cases(expr.tpe, thisV, klass) @@ -1368,12 +1418,12 @@ object Semantic: eval(qual, thisV, klass) val res = eval(rhs, thisV, klass) extendTrace(expr) { - res.ensureHot("The RHS of reassignment must be fully initialized. Found = " + res.show + ". ") + res.ensureHot("The RHS of reassignment must be hot. Found = " + res.show + ". ") } case id: Ident => val res = eval(rhs, thisV, klass) extendTrace(expr) { - res.ensureHot("The RHS of reassignment must be fully initialized. Found = " + res.show + ". ") + res.ensureHot("The RHS of reassignment must be hot. Found = " + res.show + ". ") } case closureDef(ddef) => @@ -1396,14 +1446,14 @@ object Semantic: case Match(selector, cases) => val res = eval(selector, thisV, klass) extendTrace(selector) { - res.ensureHot("The value to be matched needs to be fully initialized. Found = " + res.show + ". ") + res.ensureHot("The value to be matched needs to be hot. Found = " + res.show + ". ") } eval(cases.map(_.body), thisV, klass).join case Return(expr, from) => val res = eval(expr, thisV, klass) extendTrace(expr) { - res.ensureHot("return expression must be fully initialized. Found = " + res.show + ". ") + res.ensureHot("return expression must be hot. Found = " + res.show + ". ") } case WhileDo(cond, body) => @@ -1453,7 +1503,7 @@ object Semantic: Hot case _ => - report.error("[Internal error] unexpected tree", expr) + report.error("[Internal error] unexpected tree" + Trace.show, expr) Hot /** Handle semantics of leaf nodes */ @@ -1466,7 +1516,7 @@ object Semantic: thisV.accessLocal(tmref, klass) case tmref: TermRef => - cases(tmref.prefix, thisV, klass).select(tmref.symbol) + cases(tmref.prefix, thisV, klass).select(tmref.symbol, receiver = tmref.prefix) case tp @ ThisType(tref) => val cls = tref.classSymbol.asClass @@ -1482,7 +1532,7 @@ object Semantic: Hot case _ => - report.error("[Internal error] unexpected type " + tp, trace.toVector.last) + report.error("[Internal error] unexpected type " + tp + Trace.show, Trace.position) Hot } @@ -1497,15 +1547,15 @@ object Semantic: val obj = ref.objekt val outerCls = klass.owner.lexicallyEnclosingClass.asClass if !obj.hasOuter(klass) then - val error = PromoteError("[Internal error] outer not yet initialized, target = " + target + ", klass = " + klass + ", object = " + obj, trace.toVector) - report.error(error.show, trace.toVector.last) + val error = "[Internal error] outer not yet initialized, target = " + target + ", klass = " + klass + ", object = " + obj + Trace.show + report.error(error, Trace.position) Hot else resolveThis(target, obj.outer(klass), outerCls) case RefSet(refs) => refs.map(ref => resolveThis(target, ref, klass)).join case fun: Fun => - report.error("[Internal error] unexpected thisV = " + thisV + ", target = " + target.show + ", klass = " + klass.show, trace.toVector.last) + report.error("[Internal error] unexpected thisV = " + thisV + ", target = " + target.show + ", klass = " + klass.show + Trace.show, Trace.position) Cold case Cold => Cold diff --git a/docs/_docs/reference/other-new-features/safe-initialization.md b/docs/_docs/reference/other-new-features/safe-initialization.md index 54a7556d615f..757038eac786 100644 --- a/docs/_docs/reference/other-new-features/safe-initialization.md +++ b/docs/_docs/reference/other-new-features/safe-initialization.md @@ -235,7 +235,7 @@ A value `v` is _effectively hot_ if any of the following is true: - The root object (refered by `ThisRef`) is _effectively hot_. An effectively hot value can be regarded as transitively initialized thus can -be safely leaked via method arguments or as RHS of an reassignment. +be safely leaked via method arguments or as RHS of reassignment. The initialization checker tries to promote non-hot values to effectively hot whenenver possible. diff --git a/tests/init/neg/closureLeak.check b/tests/init/neg/closureLeak.check index 9d8e0c6925bc..db3ed8eea37b 100644 --- a/tests/init/neg/closureLeak.check +++ b/tests/init/neg/closureLeak.check @@ -1,16 +1,16 @@ -- Error: tests/init/neg/closureLeak.scala:11:14 ----------------------------------------------------------------------- 11 | l.foreach(a => a.addX(this)) // error | ^^^^^^^^^^^^^^^^^ - | Cannot prove the argument is fully initialized. Only fully initialized values are safe to leak. - | Found = Fun { this = ThisRef[class Outer], owner = class Outer }. Calling trace: + | Cannot prove the method argument is hot. Only hot values are safe to leak. + | Found = Fun { this = ThisRef[class Outer], owner = class Outer }. Calling trace: | -> class Outer { [ closureLeak.scala:1 ] | ^ | -> l.foreach(a => a.addX(this)) // error [ closureLeak.scala:11 ] | ^^^^^^^^^^^^^^^^^ | - | Promoting the value to fully initialized failed due to the following problem: - | Cannot prove the argument is fully initialized. Only fully initialized values are safe to leak. - | Found = ThisRef[class Outer]. - | Non initialized field(s): value p. Calling trace: + | Promoting the value to hot failed due to the following problem: + | Cannot prove the method argument is hot. Only hot values are safe to leak. + | Found = ThisRef[class Outer]. + | Non initialized field(s): value p. Promotion trace: | -> l.foreach(a => a.addX(this)) // error [ closureLeak.scala:11 ] | ^^^^ diff --git a/tests/init/neg/cycle-structure.check b/tests/init/neg/cycle-structure.check index 1f62dbdc0d55..79eab40be867 100644 --- a/tests/init/neg/cycle-structure.check +++ b/tests/init/neg/cycle-structure.check @@ -1,7 +1,7 @@ -- Error: tests/init/neg/cycle-structure.scala:3:13 -------------------------------------------------------------------- 3 | val x = B(this) // error | ^^^^^^^ - | Problematic object instantiation: arg 1 is not fully initialized. Calling trace: + | Problematic object instantiation: arg 1 is not hot. Calling trace: | -> case class A(b: B) { [ cycle-structure.scala:1 ] | ^ | -> val x = B(this) // error [ cycle-structure.scala:3 ] @@ -16,7 +16,7 @@ -- Error: tests/init/neg/cycle-structure.scala:9:13 -------------------------------------------------------------------- 9 | val x = A(this) // error | ^^^^^^^ - | Problematic object instantiation: arg 1 is not fully initialized. Calling trace: + | Problematic object instantiation: arg 1 is not hot. Calling trace: | -> case class B(a: A) { [ cycle-structure.scala:7 ] | ^ | -> val x = A(this) // error [ cycle-structure.scala:9 ] diff --git a/tests/init/neg/default-this.check b/tests/init/neg/default-this.check index 22dfa855675b..6d08a64450d4 100644 --- a/tests/init/neg/default-this.check +++ b/tests/init/neg/default-this.check @@ -1,8 +1,8 @@ -- Error: tests/init/neg/default-this.scala:9:8 ------------------------------------------------------------------------ 9 | compare() // error | ^^^^^^^ - | Cannot prove the argument is fully initialized. Only fully initialized values are safe to leak. - | Found = ThisRef[class B]. + | Cannot prove the method argument is hot. Only hot values are safe to leak. + | Found = ThisRef[class B]. | Non initialized field(s): value result. Calling trace: | -> class B extends A { [ default-this.scala:6 ] | ^ diff --git a/tests/init/neg/i15363.check b/tests/init/neg/i15363.check index 0d5c55acc0af..e6d0d74e9618 100644 --- a/tests/init/neg/i15363.check +++ b/tests/init/neg/i15363.check @@ -1,7 +1,7 @@ -- Error: tests/init/neg/i15363.scala:3:10 ----------------------------------------------------------------------------- 3 | val b = new B(this) // error | ^^^^^^^^^^^ - | Problematic object instantiation: arg 1 is not fully initialized. Calling trace: + | Problematic object instantiation: arg 1 is not hot. Calling trace: | -> class A: [ i15363.scala:1 ] | ^ | -> val b = new B(this) // error [ i15363.scala:3 ] diff --git a/tests/init/neg/i15459.check b/tests/init/neg/i15459.check index f34af620e537..93ba28554895 100644 --- a/tests/init/neg/i15459.check +++ b/tests/init/neg/i15459.check @@ -1,8 +1,8 @@ -- Error: tests/init/neg/i15459.scala:3:10 ----------------------------------------------------------------------------- 3 | println(this) // error | ^^^^ - | Cannot prove the argument is fully initialized. Only fully initialized values are safe to leak. - | Found = ThisRef[class Sub]. + | Cannot prove the method argument is hot. Only hot values are safe to leak. + | Found = ThisRef[class Sub]. | Non initialized field(s): value b. Calling trace: | -> class Sub extends Sup: [ i15459.scala:5 ] | ^ diff --git a/tests/init/neg/inherit-non-hot.check b/tests/init/neg/inherit-non-hot.check index 9f204c3d677d..fd25876cb38e 100644 --- a/tests/init/neg/inherit-non-hot.check +++ b/tests/init/neg/inherit-non-hot.check @@ -1,15 +1,17 @@ -- Error: tests/init/neg/inherit-non-hot.scala:6:32 -------------------------------------------------------------------- 6 | if b == null then b = new B(this) // error | ^^^^^^^^^^^^^^^ - |The RHS of reassignment must be fully initialized. Found = Warm[class B] { outer = Hot, args = (Cold) }. Calling trace: - |-> class C extends A { [ inherit-non-hot.scala:15 ] - | ^ - |-> val bAgain = toB.getBAgain [ inherit-non-hot.scala:16 ] - | ^^^ - |-> def toB: B = [ inherit-non-hot.scala:5 ] - | ^ - |-> if b == null then b = new B(this) // error [ inherit-non-hot.scala:6 ] - | ^^^^^^^^^^^^^^^ + | The RHS of reassignment must be hot. Found = Warm[class B] { outer = Hot, args = (Cold) }. Calling trace: + | -> class C extends A { [ inherit-non-hot.scala:15 ] + | ^ + | -> val bAgain = toB.getBAgain [ inherit-non-hot.scala:16 ] + | ^^^ + | -> def toB: B = [ inherit-non-hot.scala:5 ] + | ^ + | -> if b == null then b = new B(this) // error [ inherit-non-hot.scala:6 ] + | ^^^^^^^^^^^^^^^ | - |Promoting the value to fully initialized failed due to the following problem: - |Cannot prove that the field value a is fully initialized. Found = Cold. + | Promoting the value to hot failed due to the following problem: + | Cannot prove that the field value a is hot. Found = Cold. Promotion trace: + | -> class B(a: A) { [ inherit-non-hot.scala:10 ] + | ^^^^ diff --git a/tests/init/neg/inlined-method.check b/tests/init/neg/inlined-method.check index 57d6d63a8033..62bec184b825 100644 --- a/tests/init/neg/inlined-method.check +++ b/tests/init/neg/inlined-method.check @@ -1,12 +1,12 @@ -- Error: tests/init/neg/inlined-method.scala:8:45 --------------------------------------------------------------------- 8 | scala.runtime.Scala3RunTime.assertFailed(message) // error | ^^^^^^^ - | Cannot prove the argument is fully initialized. Only fully initialized values are safe to leak. - | Found = ThisRef[class InlineError]. - | Non initialized field(s): value v. Calling trace: - | -> class InlineError { [ inlined-method.scala:1 ] - | ^ - | -> Assertion.failAssert(this) [ inlined-method.scala:2 ] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ - | -> scala.runtime.Scala3RunTime.assertFailed(message) // error [ inlined-method.scala:8 ] - | ^^^^^^^ + | Cannot prove the method argument is hot. Only hot values are safe to leak. + | Found = ThisRef[class InlineError]. + | Non initialized field(s): value v. Calling trace: + | -> class InlineError { [ inlined-method.scala:1 ] + | ^ + | -> Assertion.failAssert(this) [ inlined-method.scala:2 ] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | -> scala.runtime.Scala3RunTime.assertFailed(message) // error [ inlined-method.scala:8 ] + | ^^^^^^^ diff --git a/tests/init/neg/inner-first.check b/tests/init/neg/inner-first.check index 901eb143aada..e1df69fbd4a2 100644 --- a/tests/init/neg/inner-first.check +++ b/tests/init/neg/inner-first.check @@ -1,8 +1,8 @@ -- Error: tests/init/neg/inner-first.scala:3:12 ------------------------------------------------------------------------ 3 | println(this) // error | ^^^^ - | Cannot prove the argument is fully initialized. Only fully initialized values are safe to leak. - | Found = ThisRef[class B]. + | Cannot prove the method argument is hot. Only hot values are safe to leak. + | Found = ThisRef[class B]. | Non initialized field(s): value n. Calling trace: | -> class B: [ inner-first.scala:2 ] | ^ diff --git a/tests/init/neg/promotion-loop.check b/tests/init/neg/promotion-loop.check index 60393532971c..5d23841192b8 100644 --- a/tests/init/neg/promotion-loop.check +++ b/tests/init/neg/promotion-loop.check @@ -1,13 +1,15 @@ -- Error: tests/init/neg/promotion-loop.scala:16:10 -------------------------------------------------------------------- 16 | println(b) // error | ^ - | Cannot prove the argument is fully initialized. Only fully initialized values are safe to leak. - | Found = Warm[class B] { outer = ThisRef[class Test] }. Calling trace: + | Cannot prove the method argument is hot. Only hot values are safe to leak. + | Found = Warm[class B] { outer = ThisRef[class Test] }. Calling trace: | -> class Test { test => [ promotion-loop.scala:1 ] | ^ | -> println(b) // error [ promotion-loop.scala:16 ] | ^ | - | Promoting the value to fully initialized failed due to the following problem: - | Cannot prove that the field value outer is fully initialized. Found = ThisRef[class Test]. - | Non initialized field(s): value n. + | Promoting the value to hot failed due to the following problem: + | Cannot prove that the field value outer is hot. Found = ThisRef[class Test]. + | Non initialized field(s): value n. Promotion trace: + | -> val outer = test [ promotion-loop.scala:12 ] + | ^^^^^^^^^^^^^^^^ diff --git a/tests/init/neg/secondary-ctor4.check b/tests/init/neg/secondary-ctor4.check index c493bc79e372..ce3dc3e6886d 100644 --- a/tests/init/neg/secondary-ctor4.check +++ b/tests/init/neg/secondary-ctor4.check @@ -1,7 +1,7 @@ -- Error: tests/init/neg/secondary-ctor4.scala:54:14 ------------------------------------------------------------------- 54 | val c = new C(b, 5) // error | ^^^^^^^^^^^ - | Problematic object instantiation: arg 1 is not fully initialized. Calling trace: + | Problematic object instantiation: arg 1 is not hot. Calling trace: | -> class D { [ secondary-ctor4.scala:52 ] | ^ | -> val c = new C(b, 5) // error [ secondary-ctor4.scala:54 ] @@ -24,7 +24,7 @@ -- Error: tests/init/neg/secondary-ctor4.scala:42:4 -------------------------------------------------------------------- 42 | new A(new B(new D)) // error | ^^^^^^^^^^^^^^^^^^^ - | Problematic object instantiation: the outer M.this and arg 1 are not fully initialized. Calling trace: + | Problematic object instantiation: the outer M.this and arg 1 are not hot. Calling trace: | -> class N(d: D) extends M(d) { [ secondary-ctor4.scala:59 ] | ^ | -> def this(d: D) = { [ secondary-ctor4.scala:7 ] diff --git a/tests/init/neg/t3273.check b/tests/init/neg/t3273.check index 6d010cef763e..4ca79220c550 100644 --- a/tests/init/neg/t3273.check +++ b/tests/init/neg/t3273.check @@ -1,28 +1,28 @@ -- Error: tests/init/neg/t3273.scala:4:42 ------------------------------------------------------------------------------ 4 | val num1: LazyList[Int] = 1 #:: num1.map(_ + 1) // error | ^^^^^^^^^^^^^^^ - | Cannot prove the argument is fully initialized. Only fully initialized values are safe to leak. - | Found = Fun { this = ThisRef[object Test], owner = object Test }. Calling trace: + | Cannot prove the method argument is hot. Only hot values are safe to leak. + | Found = Fun { this = ThisRef[object Test], owner = object Test }. Calling trace: | -> object Test { [ t3273.scala:3 ] | ^ | -> val num1: LazyList[Int] = 1 #:: num1.map(_ + 1) // error [ t3273.scala:4 ] | ^^^^^^^^^^^^^^^ | - | Promoting the value to fully initialized failed due to the following problem: - | Access non-initialized value num1. Calling trace: + | Promoting the value to hot failed due to the following problem: + | Access non-initialized value num1. Promotion trace: | -> val num1: LazyList[Int] = 1 #:: num1.map(_ + 1) // error [ t3273.scala:4 ] | ^^^^ -- Error: tests/init/neg/t3273.scala:5:61 ------------------------------------------------------------------------------ 5 | val num2: LazyList[Int] = 1 #:: num2.iterator.map(_ + 1).to(LazyList) // error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | Cannot prove the argument is fully initialized. Only fully initialized values are safe to leak. - | Found = Fun { this = ThisRef[object Test], owner = object Test }. Calling trace: + | Cannot prove the method argument is hot. Only hot values are safe to leak. + | Found = Fun { this = ThisRef[object Test], owner = object Test }. Calling trace: | -> object Test { [ t3273.scala:3 ] | ^ | -> val num2: LazyList[Int] = 1 #:: num2.iterator.map(_ + 1).to(LazyList) // error [ t3273.scala:5 ] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - | Promoting the value to fully initialized failed due to the following problem: - | Access non-initialized value num2. Calling trace: + | Promoting the value to hot failed due to the following problem: + | Access non-initialized value num2. Promotion trace: | -> val num2: LazyList[Int] = 1 #:: num2.iterator.map(_ + 1).to(LazyList) // error [ t3273.scala:5 ] | ^^^^ diff --git a/tests/init/pos/Type.scala b/tests/init/pos/Type.scala new file mode 100644 index 000000000000..32da17849585 --- /dev/null +++ b/tests/init/pos/Type.scala @@ -0,0 +1,21 @@ +class Type: + def underlyingType = + val isProxy = this.isInstanceOf[TypeProxy] + if (isProxy) this.asInstanceOf[TypeProxy].underlying + else NoType + + def underlyingName = + val isProxy = this.isInstanceOf[TypeProxy] + if (isProxy) this.asInstanceOf[TypeProxy].name + else "" + +abstract class TypeProxy extends Type: + def underlying: Type = this + val name: String + +object NoType extends Type + +object Implicits: + object NoImplicits extends Type + println(NoImplicits) + val n = 10 diff --git a/tests/init/pos/TypeMap.scala b/tests/init/pos/TypeMap.scala new file mode 100644 index 000000000000..4394bdddfb73 --- /dev/null +++ b/tests/init/pos/TypeMap.scala @@ -0,0 +1,13 @@ +class Type + +abstract class TypeMap: + thisMap => + def apply(tp: Type): Type + def andThen(f: Type => Type): TypeMap = new TypeMap: + def apply(tp: Type) = f(thisMap(tp)) + +class Test: + val typeMap = new TypeMap: + def apply(tp: Type) = tp + println(typeMap) + val n = 10 diff --git a/tests/init/neg/scodec.scala b/tests/init/pos/scodec.scala similarity index 92% rename from tests/init/neg/scodec.scala rename to tests/init/pos/scodec.scala index 19c2983b589d..c7e00ef0d2dd 100644 --- a/tests/init/neg/scodec.scala +++ b/tests/init/pos/scodec.scala @@ -24,7 +24,7 @@ object codecs { } val codec = new Z - println(codec) // error + println(codec) // OK, can be safely promoted val n = 10 // prevent early promotion }