diff --git a/tests/pos-with-compiler-cc/dotc/CompilationUnit.scala b/tests/pos-with-compiler-cc/dotc/CompilationUnit.scala index 44ca582c3c61..ef7728387406 100644 --- a/tests/pos-with-compiler-cc/dotc/CompilationUnit.scala +++ b/tests/pos-with-compiler-cc/dotc/CompilationUnit.scala @@ -131,11 +131,11 @@ object CompilationUnit { if (!mustExist) source else if (source.file.isDirectory) { - report.error(s"expected file, received directory '${source.file.path}'") + report.error(em"expected file, received directory '${source.file.path}'") NoSource } else if (!source.file.exists) { - report.error(s"source file not found: ${source.file.path}") + report.error(em"source file not found: ${source.file.path}") NoSource } else source diff --git a/tests/pos-with-compiler-cc/dotc/Driver.scala b/tests/pos-with-compiler-cc/dotc/Driver.scala index 14a71463c66d..0af35dd4068a 100644 --- a/tests/pos-with-compiler-cc/dotc/Driver.scala +++ b/tests/pos-with-compiler-cc/dotc/Driver.scala @@ -94,7 +94,7 @@ class Driver { val newEntries: List[String] = files .flatMap { file => if !file.exists then - report.error(s"File does not exist: ${file.path}") + report.error(em"File does not exist: ${file.path}") None else file.extension match case "jar" => Some(file.path) @@ -102,10 +102,10 @@ class Driver { TastyFileUtil.getClassPath(file) match case Some(classpath) => Some(classpath) case _ => - report.error(s"Could not load classname from: ${file.path}") + report.error(em"Could not load classname from: ${file.path}") None case _ => - report.error(s"File extension is not `tasty` or `jar`: ${file.path}") + report.error(em"File extension is not `tasty` or `jar`: ${file.path}") None } .distinct diff --git a/tests/pos-with-compiler-cc/dotc/ast/Desugar.scala b/tests/pos-with-compiler-cc/dotc/ast/Desugar.scala index ba2c8f5f43e6..003f2b421dc0 100644 --- a/tests/pos-with-compiler-cc/dotc/ast/Desugar.scala +++ b/tests/pos-with-compiler-cc/dotc/ast/Desugar.scala @@ -6,6 +6,7 @@ import core._ import util.Spans._, Types._, Contexts._, Constants._, Names._, NameOps._, Flags._ import Symbols._, StdNames._, Trees._, ContextOps._ import Decorators._, transform.SymUtils._ +import Annotations.Annotation import NameKinds.{UniqueName, EvidenceParamName, DefaultGetterName, WildcardParamName} import typer.{Namer, Checking} import util.{Property, SourceFile, SourcePosition, Chars} @@ -117,7 +118,7 @@ object desugar { if (local.exists) (defctx.owner.thisType select local).dealiasKeepAnnots else { def msg = - s"no matching symbol for ${tp.symbol.showLocated} in ${defctx.owner} / ${defctx.effectiveScope.toList}" + em"no matching symbol for ${tp.symbol.showLocated} in ${defctx.owner} / ${defctx.effectiveScope.toList}" ErrorType(msg).assertingErrorsReported(msg) } case _ => @@ -165,32 +166,41 @@ object desugar { * * Generate setter where needed */ - def valDef(vdef0: ValDef)(using Context): Tree = { + def valDef(vdef0: ValDef)(using Context): Tree = val vdef @ ValDef(_, tpt, rhs) = vdef0 - val mods = vdef.mods - val valName = normalizeName(vdef, tpt).asTermName - val vdef1 = cpy.ValDef(vdef)(name = valName) + var mods1 = vdef.mods + + def dropInto(tpt: Tree): Tree = tpt match + case Into(tpt1) => + mods1 = vdef.mods.withAddedAnnotation( + TypedSplice( + Annotation(defn.AllowConversionsAnnot).tree.withSpan(tpt.span.startPos))) + tpt1 + case ByNameTypeTree(tpt1) => + cpy.ByNameTypeTree(tpt)(dropInto(tpt1)) + case PostfixOp(tpt1, op) if op.name == tpnme.raw.STAR => + cpy.PostfixOp(tpt)(dropInto(tpt1), op) + case _ => + tpt + + val vdef1 = cpy.ValDef(vdef)(name = valName, tpt = dropInto(tpt)) + .withMods(mods1) - if (isSetterNeeded(vdef)) { - // TODO: copy of vdef as getter needed? - // val getter = ValDef(mods, name, tpt, rhs) withPos vdef.pos? - // right now vdef maps via expandedTree to a thicket which concerns itself. - // I don't see a problem with that but if there is one we can avoid it by making a copy here. + if isSetterNeeded(vdef) then val setterParam = makeSyntheticParameter(tpt = SetterParamTree().watching(vdef)) // The rhs gets filled in later, when field is generated and getter has parameters (see Memoize miniphase) val setterRhs = if (vdef.rhs.isEmpty) EmptyTree else unitLiteral val setter = cpy.DefDef(vdef)( - name = valName.setterName, - paramss = (setterParam :: Nil) :: Nil, - tpt = TypeTree(defn.UnitType), - rhs = setterRhs - ).withMods((mods | Accessor) &~ (CaseAccessor | GivenOrImplicit | Lazy)) - .dropEndMarker() // the end marker should only appear on the getter definition + name = valName.setterName, + paramss = (setterParam :: Nil) :: Nil, + tpt = TypeTree(defn.UnitType), + rhs = setterRhs + ).withMods((vdef.mods | Accessor) &~ (CaseAccessor | GivenOrImplicit | Lazy)) + .dropEndMarker() // the end marker should only appear on the getter definition Thicket(vdef1, setter) - } else vdef1 - } + end valDef def makeImplicitParameters(tpts: List[Tree], implicitFlag: FlagSet, forPrimaryConstructor: Boolean = false)(using Context): List[ValDef] = for (tpt <- tpts) yield { @@ -911,7 +921,7 @@ object desugar { case params :: paramss1 => // `params` must have a single parameter and without `given` flag def badRightAssoc(problem: String) = - report.error(i"right-associative extension method $problem", mdef.srcPos) + report.error(em"right-associative extension method $problem", mdef.srcPos) extParamss ++ mdef.paramss params match @@ -1137,7 +1147,7 @@ object desugar { def errorOnGivenBinding(bind: Bind)(using Context): Boolean = report.error( em"""${hl("given")} patterns are not allowed in a ${hl("val")} definition, - |please bind to an identifier and use an alias given.""".stripMargin, bind) + |please bind to an identifier and use an alias given.""", bind) false def isTuplePattern(arity: Int): Boolean = pat match { @@ -1237,7 +1247,7 @@ object desugar { def checkOpaqueAlias(tree: MemberDef)(using Context): MemberDef = def check(rhs: Tree): MemberDef = rhs match case bounds: TypeBoundsTree if bounds.alias.isEmpty => - report.error(i"opaque type must have a right-hand side", tree.srcPos) + report.error(em"opaque type must have a right-hand side", tree.srcPos) tree.withMods(tree.mods.withoutFlags(Opaque)) case LambdaTypeTree(_, body) => check(body) case _ => tree diff --git a/tests/pos-with-compiler-cc/dotc/ast/DesugarEnums.scala b/tests/pos-with-compiler-cc/dotc/ast/DesugarEnums.scala index 096a885dcf32..467bd8c7b420 100644 --- a/tests/pos-with-compiler-cc/dotc/ast/DesugarEnums.scala +++ b/tests/pos-with-compiler-cc/dotc/ast/DesugarEnums.scala @@ -216,7 +216,7 @@ object DesugarEnums { case Ident(name) => val matches = tparamNames.contains(name) if (matches && (caseTypeParams.nonEmpty || vparamss.isEmpty)) - report.error(i"illegal reference to type parameter $name from enum case", tree.srcPos) + report.error(em"illegal reference to type parameter $name from enum case", tree.srcPos) matches case LambdaTypeTree(lambdaParams, body) => underBinders(lambdaParams, foldOver(x, tree)) diff --git a/tests/pos-with-compiler-cc/dotc/ast/MainProxies.scala b/tests/pos-with-compiler-cc/dotc/ast/MainProxies.scala index 040582476e96..c0cf2c0d1b81 100644 --- a/tests/pos-with-compiler-cc/dotc/ast/MainProxies.scala +++ b/tests/pos-with-compiler-cc/dotc/ast/MainProxies.scala @@ -56,7 +56,7 @@ object MainProxies { def addArgs(call: untpd.Tree, mt: MethodType, idx: Int): untpd.Tree = if (mt.isImplicitMethod) { - report.error(s"@main method cannot have implicit parameters", pos) + report.error(em"@main method cannot have implicit parameters", pos) call } else { @@ -74,7 +74,7 @@ object MainProxies { mt.resType match { case restpe: MethodType => if (mt.paramInfos.lastOption.getOrElse(NoType).isRepeatedParam) - report.error(s"varargs parameter of @main method must come last", pos) + report.error(em"varargs parameter of @main method must come last", pos) addArgs(call1, restpe, idx + args.length) case _ => call1 @@ -83,7 +83,7 @@ object MainProxies { var result: List[TypeDef] = Nil if (!mainFun.owner.isStaticOwner) - report.error(s"@main method is not statically accessible", pos) + report.error(em"@main method is not statically accessible", pos) else { var call = ref(mainFun.termRef) mainFun.info match { @@ -91,9 +91,9 @@ object MainProxies { case mt: MethodType => call = addArgs(call, mt, 0) case _: PolyType => - report.error(s"@main method cannot have type parameters", pos) + report.error(em"@main method cannot have type parameters", pos) case _ => - report.error(s"@main can only annotate a method", pos) + report.error(em"@main can only annotate a method", pos) } val errVar = Ident(nme.error) val handler = CaseDef( @@ -203,7 +203,7 @@ object MainProxies { )) (sym, paramAnnotations.toVector, defaultValueSymbols(scope, sym), stat.rawComment) :: Nil case mainAnnot :: others => - report.error(s"method cannot have multiple main annotations", mainAnnot.tree) + report.error(em"method cannot have multiple main annotations", mainAnnot.tree) Nil } case stat @ TypeDef(_, impl: Template) if stat.symbol.is(Module) => @@ -379,26 +379,26 @@ object MainProxies { end generateMainClass if (!mainFun.owner.isStaticOwner) - report.error(s"main method is not statically accessible", pos) + report.error(em"main method is not statically accessible", pos) None else mainFun.info match { case _: ExprType => Some(generateMainClass(unitToValue(ref(mainFun.termRef)), Nil, Nil)) case mt: MethodType => if (mt.isImplicitMethod) - report.error(s"main method cannot have implicit parameters", pos) + report.error(em"main method cannot have implicit parameters", pos) None else mt.resType match case restpe: MethodType => - report.error(s"main method cannot be curried", pos) + report.error(em"main method cannot be curried", pos) None case _ => Some(generateMainClass(unitToValue(Apply(ref(mainFun.termRef), argRefs(mt))), argValDefs(mt), parameterInfos(mt))) case _: PolyType => - report.error(s"main method cannot have type parameters", pos) + report.error(em"main method cannot have type parameters", pos) None case _ => - report.error(s"main can only annotate a method", pos) + report.error(em"main can only annotate a method", pos) None } } diff --git a/tests/pos-with-compiler-cc/dotc/ast/TreeInfo.scala b/tests/pos-with-compiler-cc/dotc/ast/TreeInfo.scala index d17bfd0f7564..b650a0088de4 100644 --- a/tests/pos-with-compiler-cc/dotc/ast/TreeInfo.scala +++ b/tests/pos-with-compiler-cc/dotc/ast/TreeInfo.scala @@ -743,8 +743,6 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => Some(meth) case Block(Nil, expr) => unapply(expr) - case Inlined(_, bindings, expr) if bindings.forall(isPureBinding) => - unapply(expr) case _ => None } diff --git a/tests/pos-with-compiler-cc/dotc/ast/Trees.scala b/tests/pos-with-compiler-cc/dotc/ast/Trees.scala index 7a6a8df45db6..1e7876dfa71f 100644 --- a/tests/pos-with-compiler-cc/dotc/ast/Trees.scala +++ b/tests/pos-with-compiler-cc/dotc/ast/Trees.scala @@ -49,7 +49,7 @@ object Trees { * nodes. */ abstract class Tree[+T <: Untyped](implicit @constructorOnly src: SourceFile) - extends Positioned, SrcPos, Product, Attachment.Container, printing.Showable, caps.Pure { + extends Positioned, SrcPos, Product, Attachment.Container, printing.Showable { if (Stats.enabled) ntrees += 1 @@ -1747,7 +1747,7 @@ object Trees { val denot = receiver.tpe.member(method) if !denot.exists then overload.println(i"members = ${receiver.tpe.decls}") - report.error(i"no member $receiver . $method", receiver.srcPos) + report.error(em"no member $receiver . $method", receiver.srcPos) val selected = if (denot.isOverloaded) { def typeParamCount(tp: Type) = tp.widen match { diff --git a/tests/pos-with-compiler-cc/dotc/ast/tpd.scala b/tests/pos-with-compiler-cc/dotc/ast/tpd.scala index 1f43daec4d37..490e53d423bf 100644 --- a/tests/pos-with-compiler-cc/dotc/ast/tpd.scala +++ b/tests/pos-with-compiler-cc/dotc/ast/tpd.scala @@ -429,7 +429,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { else val res = Select(TypeTree(pre), tp) if needLoad && !res.symbol.isStatic then - throw new TypeError(em"cannot establish a reference to $res") + throw TypeError(em"cannot establish a reference to $res") res def ref(sym: Symbol)(using Context): Tree = @@ -1297,7 +1297,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { else if (tree.tpe.widen isRef numericCls) tree else { - report.warning(i"conversion from ${tree.tpe.widen} to ${numericCls.typeRef} will always fail at runtime.") + report.warning(em"conversion from ${tree.tpe.widen} to ${numericCls.typeRef} will always fail at runtime.") Throw(New(defn.ClassCastExceptionClass.typeRef, Nil)).withSpan(tree.span) } } diff --git a/tests/pos-with-compiler-cc/dotc/ast/untpd.scala b/tests/pos-with-compiler-cc/dotc/ast/untpd.scala index 79145551382f..33c41180fa1b 100644 --- a/tests/pos-with-compiler-cc/dotc/ast/untpd.scala +++ b/tests/pos-with-compiler-cc/dotc/ast/untpd.scala @@ -119,6 +119,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case class ContextBounds(bounds: TypeBoundsTree, cxBounds: List[Tree])(implicit @constructorOnly src: SourceFile) extends TypTree case class PatDef(mods: Modifiers, pats: List[Tree], tpt: Tree, rhs: Tree)(implicit @constructorOnly src: SourceFile) extends DefTree case class ExtMethods(paramss: List[ParamClause], methods: List[Tree])(implicit @constructorOnly src: SourceFile) extends Tree + case class Into(tpt: Tree)(implicit @constructorOnly src: SourceFile) extends Tree case class MacroTree(expr: Tree)(implicit @constructorOnly src: SourceFile) extends Tree case class ImportSelector(imported: Ident, renamed: Tree = EmptyTree, bound: Tree = EmptyTree)(implicit @constructorOnly src: SourceFile) extends Tree { @@ -651,6 +652,9 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def ExtMethods(tree: Tree)(paramss: List[ParamClause], methods: List[Tree])(using Context): Tree = tree match case tree: ExtMethods if (paramss eq tree.paramss) && (methods == tree.methods) => tree case _ => finalize(tree, untpd.ExtMethods(paramss, methods)(tree.source)) + def Into(tree: Tree)(tpt: Tree)(using Context): Tree = tree match + case tree: Into if tpt eq tree.tpt => tree + case _ => finalize(tree, untpd.Into(tpt)(tree.source)) def ImportSelector(tree: Tree)(imported: Ident, renamed: Tree, bound: Tree)(using Context): Tree = tree match { case tree: ImportSelector if (imported eq tree.imported) && (renamed eq tree.renamed) && (bound eq tree.bound) => tree case _ => finalize(tree, untpd.ImportSelector(imported, renamed, bound)(tree.source)) @@ -720,6 +724,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { cpy.PatDef(tree)(mods, transform(pats), transform(tpt), transform(rhs)) case ExtMethods(paramss, methods) => cpy.ExtMethods(tree)(transformParamss(paramss), transformSub(methods)) + case Into(tpt) => + cpy.Into(tree)(transform(tpt)) case ImportSelector(imported, renamed, bound) => cpy.ImportSelector(tree)(transformSub(imported), transform(renamed), transform(bound)) case Number(_, _) | TypedSplice(_) => @@ -780,6 +786,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { this(this(this(x, pats), tpt), rhs) case ExtMethods(paramss, methods) => this(paramss.foldLeft(x)(apply), methods) + case Into(tpt) => + this(x, tpt) case ImportSelector(imported, renamed, bound) => this(this(this(x, imported), renamed), bound) case Number(_, _) => diff --git a/tests/pos-with-compiler-cc/dotc/cc/CaptureSet.scala b/tests/pos-with-compiler-cc/dotc/cc/CaptureSet.scala index 30d25979f87e..6c2bdb468f19 100644 --- a/tests/pos-with-compiler-cc/dotc/cc/CaptureSet.scala +++ b/tests/pos-with-compiler-cc/dotc/cc/CaptureSet.scala @@ -549,7 +549,7 @@ object CaptureSet: else CompareResult.fail(this) } .andAlso { - if (origin ne source) && mapIsIdempotent then + if (origin ne source) && (origin ne initial) && mapIsIdempotent then // `tm` is idempotent, propagate back elems from image set. // This is sound, since we know that for `r in newElems: tm(r) = r`, hence // `r` is _one_ possible solution in `source` that would make an `r` appear in this set. @@ -562,7 +562,7 @@ object CaptureSet: // elements from variable sources in contra- and non-variant positions. In essence, // we approximate types resulting from such maps by returning a possible super type // from the actual type. But this is neither sound nor complete. - report.warning(i"trying to add elems ${CaptureSet(newElems)} from unrecognized source $origin of mapped set $this$whereCreated") + report.warning(em"trying to add elems ${CaptureSet(newElems)} from unrecognized source $origin of mapped set $this$whereCreated") CompareResult.fail(this) else CompareResult.OK diff --git a/tests/pos-with-compiler-cc/dotc/cc/CheckCaptures.scala b/tests/pos-with-compiler-cc/dotc/cc/CheckCaptures.scala index 9e3a3e348824..3e5d458d7e02 100644 --- a/tests/pos-with-compiler-cc/dotc/cc/CheckCaptures.scala +++ b/tests/pos-with-compiler-cc/dotc/cc/CheckCaptures.scala @@ -204,7 +204,7 @@ class CheckCaptures extends Recheck, SymTransformer: def checkElem(elem: CaptureRef, cs: CaptureSet, pos: SrcPos)(using Context) = val res = elem.singletonCaptureSet.subCaptures(cs, frozen = false) if !res.isOK then - report.error(i"$elem cannot be referenced here; it is not included in the allowed capture set ${res.blocking}", pos) + report.error(em"$elem cannot be referenced here; it is not included in the allowed capture set ${res.blocking}", pos) /** Check subcapturing `cs1 <: cs2`, report error on failure */ def checkSubset(cs1: CaptureSet, cs2: CaptureSet, pos: SrcPos)(using Context) = @@ -213,7 +213,7 @@ class CheckCaptures extends Recheck, SymTransformer: def header = if cs1.elems.size == 1 then i"reference ${cs1.elems.toList}%, % is not" else i"references $cs1 are not all" - report.error(i"$header included in allowed capture set ${res.blocking}", pos) + report.error(em"$header included in allowed capture set ${res.blocking}", pos) /** The current environment */ private var curEnv: Env = Env(NoSymbol, nestedInOwner = false, CaptureSet.empty, isBoxed = false, null) @@ -607,11 +607,28 @@ class CheckCaptures extends Recheck, SymTransformer: /** Massage `actual` and `expected` types using the methods below before checking conformance */ override def checkConformsExpr(actual: Type, expected: Type, tree: Tree)(using Context): Unit = - val expected1 = addOuterRefs(expected, actual) + val expected1 = alignDependentFunction(addOuterRefs(expected, actual), actual.stripCapturing) val actual1 = adaptBoxed(actual, expected1, tree.srcPos) //println(i"check conforms $actual1 <<< $expected1") super.checkConformsExpr(actual1, expected1, tree) + private def toDepFun(args: List[Type], resultType: Type, isContextual: Boolean, isErased: Boolean)(using Context): Type = + MethodType.companion(isContextual = isContextual, isErased = isErased)(args, resultType) + .toFunctionType(isJava = false, alwaysDependent = true) + + /** Turn `expected` into a dependent function when `actual` is dependent. */ + private def alignDependentFunction(expected: Type, actual: Type)(using Context): Type = + def recur(expected: Type): Type = expected.dealias match + case expected @ CapturingType(eparent, refs) => + CapturingType(recur(eparent), refs, boxed = expected.isBoxed) + case expected @ defn.FunctionOf(args, resultType, isContextual, isErased) + if defn.isNonRefinedFunction(expected) && defn.isFunctionType(actual) && !defn.isNonRefinedFunction(actual) => + val expected1 = toDepFun(args, resultType, isContextual, isErased) + expected1 + case _ => + expected + recur(expected) + /** For the expected type, implement the rule outlined in #14390: * - when checking an expression `a: Ca Ta` against an expected type `Ce Te`, * - where the capture set `Ce` contains Cls.this, @@ -868,21 +885,88 @@ class CheckCaptures extends Recheck, SymTransformer: // Forbid inferred self types unless they are already implied by an explicit // self type in a parent. report.error( - i"""$root needs an explicitly declared self type since its - |inferred self type $selfType - |is not visible in other compilation units that define subclasses.""", + em"""$root needs an explicitly declared self type since its + |inferred self type $selfType + |is not visible in other compilation units that define subclasses.""", root.srcPos) case _ => parentTrees -= root capt.println(i"checked $root with $selfType") end checkSelfTypes + /** Heal ill-formed capture sets in the type parameter. + * + * We can push parameter refs into a capture set in type parameters + * that this type parameter can't see. + * For example, when capture checking the following expression: + * + * def usingLogFile[T](op: (f: {*} File) => T): T = ... + * + * usingLogFile[box ?1 () -> Unit] { (f: {*} File) => () => { f.write(0) } } + * + * We may propagate `f` into ?1, making ?1 ill-formed. + * This also causes soundness issues, since `f` in ?1 should be widened to `*`, + * giving rise to an error that `*` cannot be included in a boxed capture set. + * + * To solve this, we still allow ?1 to capture parameter refs like `f`, but + * compensate this by pushing the widened capture set of `f` into ?1. + * This solves the soundness issue caused by the ill-formness of ?1. + */ + private def healTypeParam(tree: Tree)(using Context): Unit = + val checker = new TypeTraverser: + private def isAllowed(ref: CaptureRef): Boolean = ref match + case ref: TermParamRef => allowed.contains(ref) + case _ => true + + // Widen the given term parameter refs x₁ : C₁ S₁ , ⋯ , xₙ : Cₙ Sₙ to their capture sets C₁ , ⋯ , Cₙ. + // + // If in these capture sets there are any capture references that are term parameter references we should avoid, + // we will widen them recursively. + private def widenParamRefs(refs: List[TermParamRef]): List[CaptureSet] = + @scala.annotation.tailrec + def recur(todos: List[TermParamRef], acc: List[CaptureSet]): List[CaptureSet] = + todos match + case Nil => acc + case ref :: rem => + val cs = ref.captureSetOfInfo + val nextAcc = cs.filter(isAllowed(_)) :: acc + val nextRem: List[TermParamRef] = (cs.elems.toList.filter(!isAllowed(_)) ++ rem).asInstanceOf + recur(nextRem, nextAcc) + recur(refs, Nil) + + private def healCaptureSet(cs: CaptureSet): Unit = + val toInclude = widenParamRefs(cs.elems.toList.filter(!isAllowed(_)).asInstanceOf) + toInclude.foreach(checkSubset(_, cs, tree.srcPos)) + + private var allowed: SimpleIdentitySet[TermParamRef] = SimpleIdentitySet.empty + + def traverse(tp: Type) = + tp match + case CapturingType(parent, refs) => + healCaptureSet(refs) + traverse(parent) + case tp @ RefinedType(parent, rname, rinfo: MethodType) if defn.isFunctionType(tp) => + traverse(rinfo) + case tp: TermLambda => + val saved = allowed + try + tp.paramRefs.foreach(allowed += _) + traverseChildren(tp) + finally allowed = saved + case _ => + traverseChildren(tp) + + if tree.isInstanceOf[InferredTypeTree] then + checker.traverse(tree.knownType) + end healTypeParam + /** Perform the following kinds of checks * - Check all explicitly written capturing types for well-formedness using `checkWellFormedPost`. * - Check that externally visible `val`s or `def`s have empty capture sets. If not, * suggest an explicit type. This is so that separate compilation (where external * symbols have empty capture sets) gives the same results as joint compilation. * - Check that arguments of TypeApplys and AppliedTypes conform to their bounds. + * - Heal ill-formed capture sets of type parameters. See `healTypeParam`. */ def postCheck(unit: tpd.Tree)(using Context): Unit = unit.foreachSubTree { @@ -923,7 +1007,8 @@ class CheckCaptures extends Recheck, SymTransformer: em"""Non-local $sym cannot have an inferred$resultStr type |$inferred |with non-empty capture set $refs. - |The type needs to be declared explicitly.""", t.srcPos) + |The type needs to be declared explicitly.""".withoutDisambiguation(), + t.srcPos) case _ => inferred.foreachPart(checkPure, StopAt.Static) case t @ TypeApply(fun, args) => @@ -935,6 +1020,8 @@ class CheckCaptures extends Recheck, SymTransformer: } checkBounds(normArgs, tl) case _ => + + args.foreach(healTypeParam(_)) case _ => } if !ctx.reporter.errorsReported then diff --git a/tests/pos-with-compiler-cc/dotc/config/Config.scala b/tests/pos-with-compiler-cc/dotc/config/Config.scala index 17e3ec352e7c..cbd50429492e 100644 --- a/tests/pos-with-compiler-cc/dotc/config/Config.scala +++ b/tests/pos-with-compiler-cc/dotc/config/Config.scala @@ -22,6 +22,11 @@ object Config { */ inline val checkConstraintsNonCyclic = false + /** Check that reverse dependencies in constraints are correct and complete. + * Can also be enabled using -Ycheck-constraint-deps. + */ + inline val checkConstraintDeps = false + /** Check that each constraint resulting from a subtype test * is satisfiable. Also check that a type variable instantiation * satisfies its constraints. @@ -184,6 +189,9 @@ object Config { /** If set, prints a trace of all symbol completions */ inline val showCompletions = false + /** If set, show variable/variable reverse dependencies when printing constraints. */ + inline val showConstraintDeps = true + /** If set, method results that are context functions are flattened by adding * the parameters of the context function results to the methods themselves. * This is an optimization that reduces closure allocations. diff --git a/tests/pos-with-compiler-cc/dotc/config/Feature.scala b/tests/pos-with-compiler-cc/dotc/config/Feature.scala index 0b3a344900df..1637c9268e30 100644 --- a/tests/pos-with-compiler-cc/dotc/config/Feature.scala +++ b/tests/pos-with-compiler-cc/dotc/config/Feature.scala @@ -31,6 +31,7 @@ object Feature: val saferExceptions = experimental("saferExceptions") val pureFunctions = experimental("pureFunctions") val captureChecking = experimental("captureChecking") + val into = experimental("into") val globalOnlyImports: Set[TermName] = Set(pureFunctions, captureChecking) @@ -130,7 +131,7 @@ object Feature: def checkExperimentalFeature(which: String, srcPos: SrcPos, note: -> String = "")(using Context) = if !isExperimentalEnabled then - report.error(i"Experimental $which may only be used with a nightly or snapshot version of the compiler$note", srcPos) + report.error(em"Experimental $which may only be used with a nightly or snapshot version of the compiler$note", srcPos) def checkExperimentalDef(sym: Symbol, srcPos: SrcPos)(using Context) = if !isExperimentalEnabled then @@ -141,7 +142,7 @@ object Feature: i"${sym.owner} is marked @experimental" else i"$sym inherits @experimental" - report.error(s"$symMsg and therefore may only be used in an experimental scope.", srcPos) + report.error(em"$symMsg and therefore may only be used in an experimental scope.", srcPos) /** Check that experimental compiler options are only set for snapshot or nightly compiler versions. */ def checkExperimentalSettings(using Context): Unit = diff --git a/tests/pos-with-compiler-cc/dotc/config/ScalaSettings.scala b/tests/pos-with-compiler-cc/dotc/config/ScalaSettings.scala index f27a10c9af3d..f7743dddda4e 100644 --- a/tests/pos-with-compiler-cc/dotc/config/ScalaSettings.scala +++ b/tests/pos-with-compiler-cc/dotc/config/ScalaSettings.scala @@ -17,7 +17,7 @@ class ScalaSettings extends SettingGroup with AllScalaSettings object ScalaSettings: // Keep synchronized with `classfileVersion` in `BCodeIdiomatic` private val minTargetVersion = 8 - private val maxTargetVersion = 19 + private val maxTargetVersion = 20 def supportedTargetVersions: List[String] = (minTargetVersion to maxTargetVersion).toList.map(_.toString) @@ -308,6 +308,7 @@ private sealed trait YSettings: val YforceSbtPhases: Setting[Boolean] = BooleanSetting("-Yforce-sbt-phases", "Run the phases used by sbt for incremental compilation (ExtractDependencies and ExtractAPI) even if the compiler is ran outside of sbt, for debugging.") val YdumpSbtInc: Setting[Boolean] = BooleanSetting("-Ydump-sbt-inc", "For every compiled foo.scala, output the API representation and dependencies used for sbt incremental compilation in foo.inc, implies -Yforce-sbt-phases.") val YcheckAllPatmat: Setting[Boolean] = BooleanSetting("-Ycheck-all-patmat", "Check exhaustivity and redundancy of all pattern matching (used for testing the algorithm).") + val YcheckConstraintDeps: Setting[Boolean] = BooleanSetting("-Ycheck-constraint-deps", "Check dependency tracking in constraints (used for testing the algorithm).") val YretainTrees: Setting[Boolean] = BooleanSetting("-Yretain-trees", "Retain trees for top-level classes, accessible from ClassSymbol#tree") val YshowTreeIds: Setting[Boolean] = BooleanSetting("-Yshow-tree-ids", "Uniquely tag all tree nodes in debugging output.") val YfromTastyIgnoreList: Setting[List[String]] = MultiStringSetting("-Yfrom-tasty-ignore-list", "file", "List of `tasty` files in jar files that will not be loaded when using -from-tasty") @@ -329,6 +330,7 @@ private sealed trait YSettings: val YrecheckTest: Setting[Boolean] = BooleanSetting("-Yrecheck-test", "Run basic rechecking (internal test only)") val YccDebug: Setting[Boolean] = BooleanSetting("-Ycc-debug", "Used in conjunction with captureChecking language import, debug info for captured references") val YccNoAbbrev: Setting[Boolean] = BooleanSetting("-Ycc-no-abbrev", "Used in conjunction with captureChecking language import, suppress type abbreviations") + val YlightweightLazyVals: Setting[Boolean] = BooleanSetting("-Ylightweight-lazy-vals", "Use experimental lightweight implementation of lazy vals") /** Area-specific debug output */ val YexplainLowlevel: Setting[Boolean] = BooleanSetting("-Yexplain-lowlevel", "When explaining type errors, show types at a lower level.") diff --git a/tests/pos-with-compiler-cc/dotc/core/Constraint.scala b/tests/pos-with-compiler-cc/dotc/core/Constraint.scala index 07b6e71cdcc9..fb87aed77c41 100644 --- a/tests/pos-with-compiler-cc/dotc/core/Constraint.scala +++ b/tests/pos-with-compiler-cc/dotc/core/Constraint.scala @@ -4,6 +4,7 @@ package core import Types._, Contexts._ import printing.Showable +import util.{SimpleIdentitySet, SimpleIdentityMap} /** Constraint over undetermined type parameters. Constraints are built * over values of the following types: @@ -128,7 +129,7 @@ abstract class Constraint extends Showable { /** Is `tv` marked as hard in the constraint? */ def isHard(tv: TypeVar): Boolean - + /** The same as this constraint, but with `tv` marked as hard. */ def withHard(tv: TypeVar)(using Context): This @@ -165,15 +166,32 @@ abstract class Constraint extends Showable { */ def hasConflictingTypeVarsFor(tl: TypeLambda, that: Constraint): Boolean - /** Check that no constrained parameter contains itself as a bound */ - def checkNonCyclic()(using Context): this.type - /** Does `param` occur at the toplevel in `tp` ? * Toplevel means: the type itself or a factor in some * combination of `&` or `|` types. */ def occursAtToplevel(param: TypeParamRef, tp: Type)(using Context): Boolean + /** A string that shows the reverse dependencies maintained by this constraint + * (coDeps and contraDeps for OrderingConstraints). + */ + def depsToString(using Context): String + + /** Does the constraint restricted to variables outside `except` depend on `tv` + * in the given direction `co`? + * @param `co` If true, test whether the constraint would change if the variable is made larger + * otherwise, test whether the constraint would change if the variable is made smaller. + */ + def dependsOn(tv: TypeVar, except: TypeVars, co: Boolean)(using Context): Boolean + + /** Depending on Config settngs: + * - Under `checkConstraintsNonCyclic`, check that no constrained + * parameter contains itself as a bound. + * - Under `checkConstraintDeps`, check hat reverse dependencies in + * constraints are correct and complete. + */ + def checkWellFormed()(using Context): this.type + /** Check that constraint only refers to TypeParamRefs bound by itself */ def checkClosed()(using Context): Unit diff --git a/tests/pos-with-compiler-cc/dotc/core/ConstraintHandling.scala b/tests/pos-with-compiler-cc/dotc/core/ConstraintHandling.scala index 4ed01a5fbe0d..555f5858fb3f 100644 --- a/tests/pos-with-compiler-cc/dotc/core/ConstraintHandling.scala +++ b/tests/pos-with-compiler-cc/dotc/core/ConstraintHandling.scala @@ -152,8 +152,8 @@ trait ConstraintHandling { return param LevelAvoidMap(0, maxLevel)(param) match case freshVar: TypeVar => freshVar.origin - case _ => throw new TypeError( - i"Could not decrease the nesting level of ${param} from ${nestingLevel(param)} to $maxLevel in $constraint") + case _ => throw TypeError( + em"Could not decrease the nesting level of ${param} from ${nestingLevel(param)} to $maxLevel in $constraint") def nonParamBounds(param: TypeParamRef)(using Context): TypeBounds = constraint.nonParamBounds(param) diff --git a/tests/pos-with-compiler-cc/dotc/core/Contexts.scala b/tests/pos-with-compiler-cc/dotc/core/Contexts.scala index c37711593c68..5bbbf8175b5b 100644 --- a/tests/pos-with-compiler-cc/dotc/core/Contexts.scala +++ b/tests/pos-with-compiler-cc/dotc/core/Contexts.scala @@ -256,7 +256,7 @@ object Contexts { file catch case ex: InvalidPathException => - report.error(s"invalid file path: ${ex.getMessage}") + report.error(em"invalid file path: ${ex.getMessage}") NoAbstractFile /** AbstractFile with given path, memoized */ diff --git a/tests/pos-with-compiler-cc/dotc/core/Decorators.scala b/tests/pos-with-compiler-cc/dotc/core/Decorators.scala index 444b1b110e12..c3098d71f869 100644 --- a/tests/pos-with-compiler-cc/dotc/core/Decorators.scala +++ b/tests/pos-with-compiler-cc/dotc/core/Decorators.scala @@ -12,7 +12,7 @@ import transform.MegaPhase import reporting.{Message, NoExplanation} import language.experimental.pureFunctions -/** This object provides useful implicit decorators for types defined elsewhere */ +/** This object provides useful extension methods for types defined elsewhere */ object Decorators { /** Extension methods for toType/TermName methods on PreNames. @@ -59,8 +59,11 @@ object Decorators { padding + s.replace("\n", "\n" + padding) end extension + /** Convert lazy string to message. To be with caution, since no message-defined + * formatting will be done on the string. + */ extension (str: -> String) - def toMessage: Message = reporting.NoExplanation(str) + def toMessage: Message = NoExplanation(str)(using NoContext) /** Implements a findSymbol method on iterators of Symbols that * works like find but avoids Option, replacing None with NoSymbol. @@ -79,7 +82,7 @@ object Decorators { /** Implements filterConserve, zipWithConserve methods * on lists that avoid duplication of list nodes where feasible. */ - implicit class ListDecorator[T](val xs: List[T]) extends AnyVal { + extension [T](xs: List[T]) final def mapconserve[U](f: T => U): List[U] = { @tailrec @@ -208,11 +211,7 @@ object Decorators { } /** Union on lists seen as sets */ - def | (ys: List[T]): List[T] = xs ::: (ys filterNot (xs contains _)) - - /** Intersection on lists seen as sets */ - def & (ys: List[T]): List[T] = xs filter (ys contains _) - } + def setUnion (ys: List[T]): List[T] = xs ::: ys.filterNot(xs contains _) extension [T, U](xss: List[List[T]]) def nestedMap(f: T => U): List[List[U]] = xss match @@ -270,8 +269,10 @@ object Decorators { catch case ex: CyclicReference => "... (caught cyclic reference) ..." case NonFatal(ex) - if !ctx.mode.is(Mode.PrintShowExceptions) && !ctx.settings.YshowPrintErrors.value => - val msg = ex match { case te: TypeError => te.toMessage case _ => ex.getMessage } + if !ctx.mode.is(Mode.PrintShowExceptions) && !ctx.settings.YshowPrintErrors.value => + val msg = ex match + case te: TypeError => te.toMessage.message + case _ => ex.getMessage s"[cannot display due to $msg, raw string = $x]" case _ => String.valueOf(x).nn @@ -283,7 +284,7 @@ object Decorators { assert(ctx.reporter.errorsReported) x } - def assertingErrorsReported(msg: => String)(using Context): T = { + def assertingErrorsReported(msg: Message)(using Context): T = { assert(ctx.reporter.errorsReported, msg) x } @@ -293,21 +294,16 @@ object Decorators { if (xs.head eq x1) && (xs.tail eq xs1) then xs else x1 :: xs1 extension (sc: StringContext) + /** General purpose string formatting */ def i(args: Shown*)(using Context): String = new StringFormatter(sc).assemble(args) - /** Formatting for error messages: Like `i` but suppress follow-on - * error messages after the first one if some of their arguments are "non-sensical". - */ - def em(args: Shown*)(using Context): String = - forErrorMessages(new StringFormatter(sc).assemble(args)) - - /** Formatting with added explanations: Like `em`, but add explanations to - * give more info about type variables and to disambiguate where needed. + /** Interpolator yielding an error message, which undergoes + * the formatting defined in Message. */ - def ex(args: Shown*)(using Context): String = - explained(new StringFormatter(sc).assemble(args)) + def em(args: Shown*)(using Context): NoExplanation = + NoExplanation(i(args*)) extension [T <: AnyRef](arr: Array[T]) def binarySearch(x: T | Null): Int = java.util.Arrays.binarySearch(arr.asInstanceOf[Array[Object | Null]], x) diff --git a/tests/pos-with-compiler-cc/dotc/core/Definitions.scala b/tests/pos-with-compiler-cc/dotc/core/Definitions.scala index 9c1ccf531ea4..1b47e3e55b98 100644 --- a/tests/pos-with-compiler-cc/dotc/core/Definitions.scala +++ b/tests/pos-with-compiler-cc/dotc/core/Definitions.scala @@ -645,6 +645,8 @@ class Definitions { @tu lazy val RepeatedParamClass: ClassSymbol = enterSpecialPolyClass(tpnme.REPEATED_PARAM_CLASS, Covariant, Seq(ObjectType, SeqType)) + @tu lazy val IntoType: TypeSymbol = enterAliasType(tpnme.INTO, HKTypeLambda(TypeBounds.empty :: Nil)(_.paramRefs(0))) + // fundamental classes @tu lazy val StringClass: ClassSymbol = requiredClass("java.lang.String") def StringType: Type = StringClass.typeRef @@ -733,6 +735,10 @@ class Definitions { } def JavaEnumType = JavaEnumClass.typeRef + @tu lazy val MethodHandleClass: ClassSymbol = requiredClass("java.lang.invoke.MethodHandle") + @tu lazy val MethodHandlesLookupClass: ClassSymbol = requiredClass("java.lang.invoke.MethodHandles.Lookup") + @tu lazy val VarHandleClass: ClassSymbol = requiredClass("java.lang.invoke.VarHandle") + @tu lazy val StringBuilderClass: ClassSymbol = requiredClass("scala.collection.mutable.StringBuilder") @tu lazy val MatchErrorClass : ClassSymbol = requiredClass("scala.MatchError") @tu lazy val ConversionClass : ClassSymbol = requiredClass("scala.Conversion").typeRef.symbol.asClass @@ -974,6 +980,7 @@ class Definitions { @tu lazy val RefiningAnnotationClass: ClassSymbol = requiredClass("scala.annotation.RefiningAnnotation") // Annotation classes + @tu lazy val AllowConversionsAnnot: ClassSymbol = requiredClass("scala.annotation.allowConversions") @tu lazy val AnnotationDefaultAnnot: ClassSymbol = requiredClass("scala.annotation.internal.AnnotationDefault") @tu lazy val BeanPropertyAnnot: ClassSymbol = requiredClass("scala.beans.BeanProperty") @tu lazy val BooleanBeanPropertyAnnot: ClassSymbol = requiredClass("scala.beans.BooleanBeanProperty") @@ -2006,6 +2013,7 @@ class Definitions { orType, RepeatedParamClass, ByNameParamClass2x, + IntoType, AnyValClass, NullClass, NothingClass, @@ -2034,6 +2042,12 @@ class Definitions { addSyntheticSymbolsComments } + /** Definitions used in Lazy Vals implementation */ + val LazyValsModuleName = "scala.runtime.LazyVals" + @tu lazy val LazyValsModule = requiredModule(LazyValsModuleName) + @tu lazy val LazyValsWaitingState = requiredClass(s"$LazyValsModuleName.Waiting") + @tu lazy val LazyValsControlState = requiredClass(s"$LazyValsModuleName.LazyValControlState") + def addSyntheticSymbolsComments(using Context): Unit = def add(sym: Symbol, doc: String) = ctx.docCtx.foreach(_.addDocstring(sym, Some(Comment(NoSpan, doc)))) diff --git a/tests/pos-with-compiler-cc/dotc/core/Denotations.scala b/tests/pos-with-compiler-cc/dotc/core/Denotations.scala index 5d99118e56af..c78519eae227 100644 --- a/tests/pos-with-compiler-cc/dotc/core/Denotations.scala +++ b/tests/pos-with-compiler-cc/dotc/core/Denotations.scala @@ -301,9 +301,9 @@ object Denotations { case NoDenotation | _: NoQualifyingRef | _: MissingRef => def argStr = if (args.isEmpty) "" else i" matching ($args%, %)" val msg = - if (site.exists) i"$site does not have a member $kind $name$argStr" - else i"missing: $kind $name$argStr" - throw new TypeError(msg) + if site.exists then em"$site does not have a member $kind $name$argStr" + else em"missing: $kind $name$argStr" + throw TypeError(msg) case denot => denot.symbol } diff --git a/tests/pos-with-compiler-cc/dotc/core/NamerOps.scala b/tests/pos-with-compiler-cc/dotc/core/NamerOps.scala index fa0a89349b5e..bba7f97db6ba 100644 --- a/tests/pos-with-compiler-cc/dotc/core/NamerOps.scala +++ b/tests/pos-with-compiler-cc/dotc/core/NamerOps.scala @@ -70,7 +70,7 @@ object NamerOps: def findModuleBuddy(name: Name, scope: Scope)(using Context) = { val it = scope.lookupAll(name).filter(_.is(Module)) if (it.hasNext) it.next() - else NoSymbol.assertingErrorsReported(s"no companion $name in $scope") + else NoSymbol.assertingErrorsReported(em"no companion $name in $scope") } /** If a class has one of these flags, it does not get a constructor companion */ diff --git a/tests/pos-with-compiler-cc/dotc/core/Names.scala b/tests/pos-with-compiler-cc/dotc/core/Names.scala index 3c3c04147de6..aebcc381059a 100644 --- a/tests/pos-with-compiler-cc/dotc/core/Names.scala +++ b/tests/pos-with-compiler-cc/dotc/core/Names.scala @@ -15,8 +15,8 @@ import scala.annotation.internal.sharable object Names { import NameKinds._ - /** Things that can be turned into names with `totermName` and `toTypeName` - * Decorators defines implements these as extension methods for strings. + /** Things that can be turned into names with `toTermName` and `toTypeName`. + * Decorators implements these as extension methods for strings. */ type PreName = Name | String diff --git a/tests/pos-with-compiler-cc/dotc/core/OrderingConstraint.scala b/tests/pos-with-compiler-cc/dotc/core/OrderingConstraint.scala index 961d106a14c8..0bf798c00540 100644 --- a/tests/pos-with-compiler-cc/dotc/core/OrderingConstraint.scala +++ b/tests/pos-with-compiler-cc/dotc/core/OrderingConstraint.scala @@ -17,27 +17,34 @@ import caps.unsafe.unsafeUnbox object OrderingConstraint { - type ArrayValuedMap[T] = SimpleIdentityMap[TypeLambda, Array[T]] + /** If true, use reverse dependencies in `replace` to avoid checking the bounds + * of all parameters in the constraint. This can speed things up, but there are some + * rare corner cases where reverse dependencies miss a parameter. Specifically, + * if a constraint contains a free reference to TypeParam P and afterwards the + * same P is added as a bound variable to the constraint, a backwards link would + * then become necessary at this point but is missing. This causes two CB projects + * to fail when reverse dependencies are checked (parboiled2 and perspective). + * In these rare cases `replace` could behave differently when optimized. However, + * no deviation was found in the two projects. It is not clear what the "right" + * behavior of `replace` should be in these cases. Normally, PolyTypes added + * to constraints are supposed to be fresh, so that would mean that the behavior + * with optimizeReplace = true would be correct. But the previous behavior without + * reverse dependency checking corresponds to `optimizeReplace = false`. This behavior + * makes sense if we assume that the added polytype was simply added too late, so we + * want to establish the link between newly bound variable and pre-existing reference. + */ + private final val optimizeReplace = true + + private type ArrayValuedMap[T] = SimpleIdentityMap[TypeLambda, Array[T]] /** The type of `OrderingConstraint#boundsMap` */ - type ParamBounds = ArrayValuedMap[Type] + private type ParamBounds = ArrayValuedMap[Type] /** The type of `OrderingConstraint#lowerMap`, `OrderingConstraint#upperMap` */ - type ParamOrdering = ArrayValuedMap[List[TypeParamRef]] - - /** A new constraint with given maps and given set of hard typevars */ - private def newConstraint( - boundsMap: ParamBounds, lowerMap: ParamOrdering, upperMap: ParamOrdering, - hardVars: TypeVars)(using Context) : OrderingConstraint = - if boundsMap.isEmpty && lowerMap.isEmpty && upperMap.isEmpty then - empty - else - val result = new OrderingConstraint(boundsMap, lowerMap, upperMap, hardVars) - if ctx.run != null then ctx.run.nn.recordConstraintSize(result, result.boundsMap.size) - result + private type ParamOrdering = ArrayValuedMap[List[TypeParamRef]] /** A lens for updating a single entry array in one of the three constraint maps */ - abstract class ConstraintLens[T <: AnyRef: ClassTag] { + private abstract class ConstraintLens[T <: AnyRef: ClassTag] { def entries(c: OrderingConstraint, poly: TypeLambda): Array[T] | Null def updateEntries(c: OrderingConstraint, poly: TypeLambda, entries: Array[T])(using Context): OrderingConstraint def initial: T @@ -48,7 +55,7 @@ object OrderingConstraint { } /** The `current` constraint but with the entry for `param` updated to `entry`. - * `current` is used linearly. If it is different from `prev` it is + * `current` is used linearly. If it is different from `prev` then `current` is * known to be dead after the call. Hence it is OK to update destructively * parts of `current` which are not shared by `prev`. */ @@ -90,27 +97,27 @@ object OrderingConstraint { map(prev, current, param.binder, param.paramNum, f) } - val boundsLens: ConstraintLens[Type] = new ConstraintLens[Type] { + private val boundsLens: ConstraintLens[Type] = new ConstraintLens[Type] { def entries(c: OrderingConstraint, poly: TypeLambda): Array[Type] | Null = c.boundsMap(poly) def updateEntries(c: OrderingConstraint, poly: TypeLambda, entries: Array[Type])(using Context): OrderingConstraint = - newConstraint(c.boundsMap.updated(poly, entries), c.lowerMap, c.upperMap, c.hardVars) + c.newConstraint(boundsMap = c.boundsMap.updated(poly, entries)) def initial = NoType } - val lowerLens: ConstraintLens[List[TypeParamRef]] = new ConstraintLens[List[TypeParamRef]] { + private val lowerLens: ConstraintLens[List[TypeParamRef]] = new ConstraintLens[List[TypeParamRef]] { def entries(c: OrderingConstraint, poly: TypeLambda): Array[List[TypeParamRef]] | Null = c.lowerMap(poly) def updateEntries(c: OrderingConstraint, poly: TypeLambda, entries: Array[List[TypeParamRef]])(using Context): OrderingConstraint = - newConstraint(c.boundsMap, c.lowerMap.updated(poly, entries), c.upperMap, c.hardVars) + c.newConstraint(lowerMap = c.lowerMap.updated(poly, entries)) def initial = Nil } - val upperLens: ConstraintLens[List[TypeParamRef]] = new ConstraintLens[List[TypeParamRef]] { + private val upperLens: ConstraintLens[List[TypeParamRef]] = new ConstraintLens[List[TypeParamRef]] { def entries(c: OrderingConstraint, poly: TypeLambda): Array[List[TypeParamRef]] | Null = c.upperMap(poly) def updateEntries(c: OrderingConstraint, poly: TypeLambda, entries: Array[List[TypeParamRef]])(using Context): OrderingConstraint = - newConstraint(c.boundsMap, c.lowerMap, c.upperMap.updated(poly, entries), c.hardVars) + c.newConstraint(upperMap = c.upperMap.updated(poly, entries)) def initial = Nil } @@ -144,11 +151,27 @@ class OrderingConstraint(private val boundsMap: ParamBounds, private val lowerMap : ParamOrdering, private val upperMap : ParamOrdering, private val hardVars : TypeVars) extends Constraint { + thisConstraint => import UnificationDirection.* type This = OrderingConstraint + /** A new constraint with given maps and given set of hard typevars */ + def newConstraint( // !!! Dotty problem: Making newConstraint `private` causes -Ytest-pickler failure. + boundsMap: ParamBounds = this.boundsMap, + lowerMap: ParamOrdering = this.lowerMap, + upperMap: ParamOrdering = this.upperMap, + hardVars: TypeVars = this.hardVars)(using Context) : OrderingConstraint = + if boundsMap.isEmpty && lowerMap.isEmpty && upperMap.isEmpty then + empty + else + val result = new OrderingConstraint(boundsMap, lowerMap, upperMap, hardVars) + if ctx.run != null then ctx.run.nn.recordConstraintSize(result, result.boundsMap.size) + result.coDeps = this.coDeps + result.contraDeps = this.contraDeps + result + // ----------- Basic indices -------------------------------------------------- /** The number of type parameters in the given entry array */ @@ -218,6 +241,197 @@ class OrderingConstraint(private val boundsMap: ParamBounds, if tvar == null then NoType else tvar +// ------------- Type parameter dependencies ---------------------------------------- + + private type ReverseDeps = SimpleIdentityMap[TypeParamRef, SimpleIdentitySet[TypeParamRef]] + + /** A map that associates type parameters of this constraint with all other type + * parameters that refer to them in their bounds covariantly, such that, if the + * type parameter is instantiated to a larger type, the constraint would be narrowed + * (i.e. solution set changes other than simply being made larger). + */ + private var coDeps: ReverseDeps = SimpleIdentityMap.empty + + /** A map that associates type parameters of this constraint with all other type + * parameters that refer to them in their bounds covariantly, such that, if the + * type parameter is instantiated to a smaller type, the constraint would be narrowed. + * (i.e. solution set changes other than simply being made larger). + */ + private var contraDeps: ReverseDeps = SimpleIdentityMap.empty + + /** Null-safe indexing */ + extension (deps: ReverseDeps) def at(param: TypeParamRef): SimpleIdentitySet[TypeParamRef] = + val result = deps(param) + if null == result // swapped operand order important since `==` is overloaded in `SimpleIdentitySet` + then SimpleIdentitySet.empty + else result + + override def dependsOn(tv: TypeVar, except: TypeVars, co: Boolean)(using Context): Boolean = + def origin(tv: TypeVar) = + assert(!instType(tv).exists) + tv.origin + val param = origin(tv) + val excluded = except.map(origin) + val qualifies: TypeParamRef => Boolean = !excluded.contains(_) + def test(deps: ReverseDeps, lens: ConstraintLens[List[TypeParamRef]]) = + deps.at(param).exists(qualifies) + || lens(this, tv.origin.binder, tv.origin.paramNum).exists(qualifies) + if co then test(coDeps, upperLens) else test(contraDeps, lowerLens) + + /** Modify traversals in two respects: + * - when encountering an application C[Ts], where C is a type variable or parameter + * that has an instantiation in this constraint, assume the type parameters of + * the instantiation instead of the type parameters of C when traversing the + * arguments Ts. That can make a difference for the variance in which an argument + * is traversed. Example constraint: + * + * constrained types: C[X], A + * A >: C[B] + * C := Option + * + * Here, B is traversed with variance +1 instead of 0. Test case: pos/t3152.scala + * + * - When typing a prefx, don't avoid negative variances. This matters only for the + * corner case where a parameter is instantiated to Nothing (see comment in + * TypeAccumulator#applyToPrefix). When determining instantiation directions in + * interpolations (which is what dependency variances are for), it can be ignored. + */ + private trait ConstraintAwareTraversal[T] extends TypeAccumulator[T]: + + /** Does `param` have bounds in the current constraint? */ + protected def hasBounds(param: TypeParamRef): Boolean = entry(param).isInstanceOf[TypeBounds] + + override def tyconTypeParams(tp: AppliedType)(using Context): List[ParamInfo] = + def tparams(tycon: Type): List[ParamInfo] = tycon match + case tycon: TypeVar if !tycon.inst.exists => tparams(tycon.origin) + case tycon: TypeParamRef if !hasBounds(tycon) => + val entryParams = entry(tycon).typeParams + if entryParams.nonEmpty then entryParams + else tp.tyconTypeParams + case _ => tp.tyconTypeParams + tparams(tp.tycon) + + override def applyToPrefix(x: T, tp: NamedType): T = + this(x, tp.prefix) + end ConstraintAwareTraversal + + /** A type traverser that adjust dependencies originating from a given type + * @param ignoreBinding if not null, a parameter that is assumed to be still uninstantiated. + * This is necessary to handle replacements. + */ + private class Adjuster(srcParam: TypeParamRef, ignoreBinding: TypeParamRef | Null)(using Context) + extends TypeTraverser, ConstraintAwareTraversal[Unit]: + + var add: Boolean = compiletime.uninitialized + val seen = util.HashSet[LazyRef]() + + override protected def hasBounds(param: TypeParamRef) = + (param eq ignoreBinding) || super.hasBounds(param) + + def update(deps: ReverseDeps, referenced: TypeParamRef): ReverseDeps = + val prev = deps.at(referenced) + val newSet = if add then prev + srcParam else prev - srcParam + if newSet.isEmpty then deps.remove(referenced) + else deps.updated(referenced, newSet) + + def traverse(t: Type) = t match + case param: TypeParamRef => + if hasBounds(param) then + if variance >= 0 then coDeps = update(coDeps, param) + if variance <= 0 then contraDeps = update(contraDeps, param) + else + traverse(entry(param)) + case tp: LazyRef => + if !seen.contains(tp) then + seen += tp + traverse(tp.ref) + case _ => traverseChildren(t) + end Adjuster + + /** Adjust dependencies to account for the delta of previous entry `prevEntry` + * and the new bound `entry` for the type parameter `srcParam`. + */ + def adjustDeps(entry: Type | Null, prevEntry: Type | Null, srcParam: TypeParamRef, ignoreBinding: TypeParamRef | Null = null)(using Context): this.type = + val adjuster = new Adjuster(srcParam, ignoreBinding) + + /** Adjust reverse dependencies of all type parameters referenced by `bound` + * @param isLower `bound` is a lower bound + * @param add if true, add referenced variables to dependencoes, otherwise drop them. + */ + def adjustReferenced(bound: Type, isLower: Boolean, add: Boolean) = + adjuster.variance = if isLower then 1 else -1 + adjuster.add = add + adjuster.seen.clear() + adjuster.traverse(bound) + + /** Use an optimized strategy to adjust dependencies to account for the delta + * of previous bound `prevBound` and new bound `bound`: If `prevBound` is some + * and/or prefix of `bound`, and `baseCase` is true, just add the new parts of `bound`. + * @param isLower `bound` and `prevBound` are lower bounds + * @return true iff the delta strategy succeeded, false if it failed in which case + * the constraint is left unchanged. + */ + def adjustDelta(bound: Type, prevBound: Type, isLower: Boolean, baseCase: => Boolean): Boolean = + if bound eq prevBound then + baseCase + else bound match + case bound: AndOrType => + adjustDelta(bound.tp1, prevBound, isLower, baseCase) && { + adjustReferenced(bound.tp2, isLower, add = true) + true + } + case _ => false + + /** Add or remove depenencies referenced in `bounds`. + * @param add if true, dependecies are added, otherwise they are removed + */ + def adjustBounds(bounds: TypeBounds, add: Boolean) = + adjustReferenced(bounds.lo, isLower = true, add) + adjustReferenced(bounds.hi, isLower = false, add) + + entry match + case entry @ TypeBounds(lo, hi) => + prevEntry match + case prevEntry @ TypeBounds(plo, phi) => + if !adjustDelta(lo, plo, isLower = true, + adjustDelta(hi, phi, isLower = false, true)) + then + adjustBounds(prevEntry, add = false) + adjustBounds(entry, add = true) + case _ => + adjustBounds(entry, add = true) + case _ => + prevEntry match + case prevEntry: TypeBounds => + adjustBounds(prevEntry, add = false) + case _ => + dropDeps(srcParam) // srcParam is instantiated, so its dependencies can be dropped + this + end adjustDeps + + /** Adjust dependencies to account for adding or dropping all `entries` associated + * with `poly`. + * @param add if true, entries is added, otherwise it is dropped + */ + def adjustDeps(poly: TypeLambda, entries: Array[Type], add: Boolean)(using Context): this.type = + for n <- 0 until paramCount(entries) do + if add + then adjustDeps(entries(n), NoType, poly.paramRefs(n)) + else adjustDeps(NoType, entries(n), poly.paramRefs(n)) + this + + /** Remove all reverse dependencies of `param` */ + def dropDeps(param: TypeParamRef)(using Context): Unit = + coDeps = coDeps.remove(param) + contraDeps = contraDeps.remove(param) + + /** A string representing the two dependency maps */ + def depsToString(using Context): String = + def depsStr(deps: ReverseDeps): String = + def depStr(param: TypeParamRef) = i"$param --> ${deps.at(param).toList}%, %" + if deps.isEmpty then "" else i"\n ${deps.toList.map((k, v) => depStr(k))}%\n %" + i" co-deps:${depsStr(coDeps)}\n contra-deps:${depsStr(contraDeps)}\n" + // ---------- Adding TypeLambdas -------------------------------------------------- /** The bound type `tp` without constrained parameters which are clearly @@ -283,7 +497,8 @@ class OrderingConstraint(private val boundsMap: ParamBounds, val entries1 = new Array[Type](nparams * 2) poly.paramInfos.copyToArray(entries1, 0) tvars.copyToArray(entries1, nparams) - newConstraint(boundsMap.updated(poly, entries1), lowerMap, upperMap, hardVars).init(poly) + newConstraint(boundsMap = this.boundsMap.updated(poly, entries1)) + .init(poly) } /** Split dependent parameters off the bounds for parameters in `poly`. @@ -299,13 +514,14 @@ class OrderingConstraint(private val boundsMap: ParamBounds, val param = poly.paramRefs(i) val bounds = dropWildcards(nonParamBounds(param)) val stripped = stripParams(bounds, todos, isUpper = true) - current = updateEntry(current, param, stripped) + current = boundsLens.update(this, current, param, stripped) while todos.nonEmpty do current = todos.head.unsafeUnbox(current, param) todos.dropInPlace(1) i += 1 } - current.checkNonCyclic() + current.adjustDeps(poly, current.boundsMap(poly).nn, add = true) + .checkWellFormed() } // ---------- Updates ------------------------------------------------------------ @@ -419,7 +635,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds, case param: TypeParamRef if contains(param) => param :: (if (isUpper) upper(param) else lower(param)) case tp: AndType if isUpper => - dependentParams(tp.tp1, isUpper) | (dependentParams(tp.tp2, isUpper)) + dependentParams(tp.tp1, isUpper).setUnion(dependentParams(tp.tp2, isUpper)) case tp: OrType if !isUpper => dependentParams(tp.tp1, isUpper).intersect(dependentParams(tp.tp2, isUpper)) case EtaExpansion(tycon) => @@ -427,10 +643,12 @@ class OrderingConstraint(private val boundsMap: ParamBounds, case _ => Nil - private def updateEntry(current: This, param: TypeParamRef, tp: Type)(using Context): This = { - if Config.checkNoWildcardsInConstraint then assert(!tp.containsWildcardTypes) - var current1 = boundsLens.update(this, current, param, tp) - tp match { + private def updateEntry(current: This, param: TypeParamRef, newEntry: Type)(using Context): This = { + if Config.checkNoWildcardsInConstraint then assert(!newEntry.containsWildcardTypes) + val oldEntry = current.entry(param) + var current1 = boundsLens.update(this, current, param, newEntry) + .adjustDeps(newEntry, oldEntry, param) + newEntry match { case TypeBounds(lo, hi) => for p <- dependentParams(lo, isUpper = false) do current1 = order(current1, p, param) @@ -443,10 +661,10 @@ class OrderingConstraint(private val boundsMap: ParamBounds, /** The public version of `updateEntry`. Guarantees that there are no cycles */ def updateEntry(param: TypeParamRef, tp: Type)(using Context): This = - updateEntry(this, param, ensureNonCyclic(param, tp)).checkNonCyclic() + updateEntry(this, param, ensureNonCyclic(param, tp)).checkWellFormed() def addLess(param1: TypeParamRef, param2: TypeParamRef, direction: UnificationDirection)(using Context): This = - order(this, param1, param2, direction).checkNonCyclic() + order(this, param1, param2, direction).checkWellFormed() // ---------- Replacements and Removals ------------------------------------- @@ -456,24 +674,78 @@ class OrderingConstraint(private val boundsMap: ParamBounds, */ def replace(param: TypeParamRef, tp: Type)(using Context): OrderingConstraint = val replacement = tp.dealiasKeepAnnots.stripTypeVar - if param == replacement then this.checkNonCyclic() + if param == replacement then this.checkWellFormed() else assert(replacement.isValueTypeOrLambda) - var current = - if isRemovable(param.binder) then remove(param.binder) - else updateEntry(this, param, replacement) - def removeParam(ps: List[TypeParamRef]) = ps.filterConserve(param ne _) - - def replaceParam(tp: Type, atPoly: TypeLambda, atIdx: Int): Type = - current.ensureNonCyclic(atPoly.paramRefs(atIdx), tp.substParam(param, replacement)) - - current.foreachParam { (p, i) => - current = boundsLens.map(this, current, p, i, replaceParam(_, p, i)) - current = lowerLens.map(this, current, p, i, removeParam) - current = upperLens.map(this, current, p, i, removeParam) - } - current.checkNonCyclic() + val replacedTypeVar = typeVarOfParam(param) + //println(i"replace $param with $replacement in $this") + + def mapReplacedTypeVarTo(to: Type) = new TypeMap: + override def apply(t: Type): Type = + if (t eq replacedTypeVar) && t.exists then to else mapOver(t) + + val coDepsOfParam = coDeps.at(param) + val contraDepsOfParam = contraDeps.at(param) + + var current = updateEntry(this, param, replacement) + // Need to update param early to avoid infinite recursion on instantiation. + // See i16311.scala for a test case. On the other hand, for the purposes of + // dependency adjustment, we need to pretend that `param` is still unbound. + // We achieve that by passing a `ignoreBinding = param` to `adjustDeps` below. + + def removeParamFrom(ps: List[TypeParamRef]) = + ps.filterConserve(param ne _) + + for lo <- lower(param) do + current = upperLens.map(this, current, lo, removeParamFrom) + for hi <- upper(param) do + current = lowerLens.map(this, current, hi, removeParamFrom) + + def replaceParamIn(other: TypeParamRef) = + val oldEntry = current.entry(other) + val newEntry = current.ensureNonCyclic(other, oldEntry.substParam(param, replacement)) + current = boundsLens.update(this, current, other, newEntry) + var oldDepEntry = oldEntry + var newDepEntry = newEntry + replacedTypeVar match + case tvar: TypeVar => + if tvar.inst.exists // `isInstantiated` would use ctx.typerState.constraint rather than the current constraint + then + // If the type variable has been instantiated, we need to forget about + // the instantiation for old dependencies. + // I.e. to find out what the old entry was, we should not follow + // the newly instantiated type variable but assume the type variable's origin `param`. + // An example where this happens is if `replace` is called from TypeVar's `instantiateWith`. + oldDepEntry = mapReplacedTypeVarTo(param)(oldDepEntry) + else + // If the type variable has not been instantiated, we need to replace references to it + // in the new entry by `replacement`. Otherwise we would get stuck in an uninstantiated + // type variable. + // An example where this happens is if `replace` is called from unify. + newDepEntry = mapReplacedTypeVarTo(replacement)(newDepEntry) + case _ => + if oldDepEntry ne newDepEntry then + current.adjustDeps(newDepEntry, oldDepEntry, other, ignoreBinding = param) + end replaceParamIn + + if optimizeReplace then + current.foreachParam { (p, i) => + val other = p.paramRefs(i) + entry(other) match + case _: TypeBounds => + if coDepsOfParam.contains(other) || contraDepsOfParam.contains(other) then + replaceParamIn(other) + case _ => replaceParamIn(other) + } + else + current.foreachParam { (p, i) => + val other = p.paramRefs(i) + if other != param then replaceParamIn(other) + } + if isRemovable(param.binder) then current = current.remove(param.binder) + current.dropDeps(param) + current.checkWellFormed() end replace def remove(pt: TypeLambda)(using Context): This = { @@ -486,7 +758,8 @@ class OrderingConstraint(private val boundsMap: ParamBounds, } val hardVars1 = pt.paramRefs.foldLeft(hardVars)((hvs, param) => hvs - typeVarOfParam(param)) newConstraint(boundsMap.remove(pt), removeFromOrdering(lowerMap), removeFromOrdering(upperMap), hardVars1) - .checkNonCyclic() + .adjustDeps(pt, boundsMap(pt).nn, add = false) + .checkWellFormed() } def isRemovable(pt: TypeLambda): Boolean = { @@ -512,7 +785,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds, def swapKey[T](m: ArrayValuedMap[T]) = val info = m(from) if info == null then m else m.remove(from).updated(to, info) - var current = newConstraint(swapKey(boundsMap), swapKey(lowerMap), swapKey(upperMap), hardVars) + var current = newConstraint(swapKey(boundsMap), swapKey(lowerMap), swapKey(upperMap)) def subst[T <: Type](x: T): T = x.subst(from, to).asInstanceOf[T] current.foreachParam {(p, i) => current = boundsLens.map(this, current, p, i, subst) @@ -520,12 +793,12 @@ class OrderingConstraint(private val boundsMap: ParamBounds, current = upperLens.map(this, current, p, i, _.map(subst)) } constr.println(i"renamed $this to $current") - current.checkNonCyclic() + current.checkWellFormed() def isHard(tv: TypeVar) = hardVars.contains(tv) def withHard(tv: TypeVar)(using Context) = - newConstraint(boundsMap, lowerMap, upperMap, hardVars + tv) + newConstraint(hardVars = this.hardVars + tv) def instType(tvar: TypeVar): Type = entry(tvar.origin) match case _: TypeBounds => NoType @@ -552,6 +825,26 @@ class OrderingConstraint(private val boundsMap: ParamBounds, assert(tvar.origin == param, i"mismatch $tvar, $param") case _ => + def occursAtToplevel(param: TypeParamRef, inst: Type)(using Context): Boolean = + def occurs(tp: Type)(using Context): Boolean = tp match + case tp: AndOrType => + occurs(tp.tp1) || occurs(tp.tp2) + case tp: TypeParamRef => + (tp eq param) || entry(tp).match + case NoType => false + case TypeBounds(lo, hi) => (lo eq hi) && occurs(lo) + case inst => occurs(inst) + case tp: TypeVar => + occurs(tp.underlying) + case TypeBounds(lo, hi) => + occurs(lo) || occurs(hi) + case _ => + val tp1 = tp.dealias + (tp1 ne tp) && occurs(tp1) + + occurs(inst) + end occursAtToplevel + // ---------- Exploration -------------------------------------------------------- def domainLambdas: List[TypeLambda] = boundsMap.keys @@ -604,7 +897,57 @@ class OrderingConstraint(private val boundsMap: ParamBounds, // ---------- Checking ----------------------------------------------- - def checkNonCyclic()(using Context): this.type = + def checkWellFormed()(using Context): this.type = + + /** Check that each dependency A -> B in coDeps and contraDeps corresponds to + * a reference to A at the right variance in the entry of B. + */ + def checkBackward(deps: ReverseDeps, depsName: String, v: Int)(using Context): Unit = + deps.foreachBinding { (param, params) => + for srcParam <- params do + assert(contains(srcParam) && occursAtVariance(param, v, in = entry(srcParam)), + i"wrong $depsName backwards reference $param -> $srcParam in $thisConstraint") + } + + /** A type traverser that checks that all references bound in the constraint + * are accounted for in coDeps and/or contraDeps. + */ + def checkForward(srcParam: TypeParamRef)(using Context) = + new TypeTraverser with ConstraintAwareTraversal[Unit]: + val seen = util.HashSet[LazyRef]() + def traverse(t: Type): Unit = t match + case param: TypeParamRef if param ne srcParam => + def check(deps: ReverseDeps, directDeps: List[TypeParamRef], depsName: String) = + assert(deps.at(param).contains(srcParam) || directDeps.contains(srcParam), + i"missing $depsName backwards reference $param -> $srcParam in $thisConstraint") + entry(param) match + case _: TypeBounds => + if variance >= 0 then check(contraDeps, upper(param), "contra") + if variance <= 0 then check(coDeps, lower(param), "co") + case tp => + traverse(tp) + case tp: LazyRef => + if !seen.contains(tp) then + seen += tp + traverse(tp.ref) + case _ => traverseChildren(t) + + /** Does `param` occur at variance `v` or else at variance 0 in entry `in`? */ + def occursAtVariance(param: TypeParamRef, v: Int, in: Type)(using Context): Boolean = + val test = new TypeAccumulator[Boolean] with ConstraintAwareTraversal[Boolean]: + def apply(x: Boolean, t: Type): Boolean = + if x then true + else t match + case t: TypeParamRef => + entry(t) match + case _: TypeBounds => + t == param && (variance == 0 || variance == v) + case e => + apply(x, e) + case _ => + foldOver(x, t) + test(false, in) + if Config.checkConstraintsNonCyclic then domainParams.foreach { param => val inst = entry(param) @@ -613,28 +956,13 @@ class OrderingConstraint(private val boundsMap: ParamBounds, assert(!occursAtToplevel(param, inst), s"cyclic bound for $param: ${inst.show} in ${this.show}") } - this - - def occursAtToplevel(param: TypeParamRef, inst: Type)(using Context): Boolean = - - def occurs(tp: Type)(using Context): Boolean = tp match - case tp: AndOrType => - occurs(tp.tp1) || occurs(tp.tp2) - case tp: TypeParamRef => - (tp eq param) || entry(tp).match - case NoType => false - case TypeBounds(lo, hi) => (lo eq hi) && occurs(lo) - case inst => occurs(inst) - case tp: TypeVar => - occurs(tp.underlying) - case TypeBounds(lo, hi) => - occurs(lo) || occurs(hi) - case _ => - val tp1 = tp.dealias - (tp1 ne tp) && occurs(tp1) + if Config.checkConstraintDeps || ctx.settings.YcheckConstraintDeps.value then + checkBackward(coDeps, "co", -1) + checkBackward(contraDeps, "contra", +1) + domainParams.foreach(p => if contains(p) then checkForward(p).traverse(entry(p))) - occurs(inst) - end occursAtToplevel + this + end checkWellFormed override def checkClosed()(using Context): Unit = @@ -664,13 +992,16 @@ class OrderingConstraint(private val boundsMap: ParamBounds, val constrainedText = " constrained types = " + domainLambdas.mkString("\n") val boundsText = - " bounds = " + { + "\n bounds = " + { val assocs = for (param <- domainParams) yield s"${param.binder.paramNames(param.paramNum)}: ${entryText(entry(param))}" assocs.mkString("\n") } - constrainedText + "\n" + boundsText + val depsText = + "\n coDeps = " + coDeps + + "\n contraDeps = " + contraDeps + constrainedText + boundsText + depsText } } diff --git a/tests/pos-with-compiler-cc/dotc/core/StdNames.scala b/tests/pos-with-compiler-cc/dotc/core/StdNames.scala index 50c96191143c..dff423fd0bb4 100644 --- a/tests/pos-with-compiler-cc/dotc/core/StdNames.scala +++ b/tests/pos-with-compiler-cc/dotc/core/StdNames.scala @@ -128,6 +128,7 @@ object StdNames { val EXCEPTION_RESULT_PREFIX: N = "exceptionResult" val EXPAND_SEPARATOR: N = str.EXPAND_SEPARATOR val IMPORT: N = "" + val INTO: N = "" val MODULE_SUFFIX: N = str.MODULE_SUFFIX val OPS_PACKAGE: N = "" val OVERLOADED: N = "" @@ -500,6 +501,7 @@ object StdNames { val info: N = "info" val inlinedEquals: N = "inlinedEquals" val internal: N = "internal" + val into: N = "into" val isArray: N = "isArray" val isDefinedAt: N = "isDefinedAt" val isDefinedAtImpl: N = "$isDefinedAt" diff --git a/tests/pos-with-compiler-cc/dotc/core/SymDenotations.scala b/tests/pos-with-compiler-cc/dotc/core/SymDenotations.scala index ad100962dca6..10abf22fbb5e 100644 --- a/tests/pos-with-compiler-cc/dotc/core/SymDenotations.scala +++ b/tests/pos-with-compiler-cc/dotc/core/SymDenotations.scala @@ -169,7 +169,8 @@ object SymDenotations { } } else { - if (myFlags.is(Touched)) throw CyclicReference(this) + if (myFlags.is(Touched)) + throw CyclicReference(this)(using ctx.withOwner(symbol)) myFlags |= Touched atPhase(validFor.firstPhaseId)(completer.complete(this)) } @@ -961,6 +962,26 @@ object SymDenotations { def isSkolem: Boolean = name == nme.SKOLEM + // Java language spec: https://docs.oracle.com/javase/specs/jls/se11/html/jls-15.html#jls-15.12.3 + // Scala 2 spec: https://scala-lang.org/files/archive/spec/2.13/06-expressions.html#signature-polymorphic-methods + def isSignaturePolymorphic(using Context): Boolean = + containsSignaturePolymorphic + && is(JavaDefined) + && hasAnnotation(defn.NativeAnnot) + && atPhase(typerPhase)(symbol.denot).paramSymss.match + case List(List(p)) => p.info.isRepeatedParam + case _ => false + + def containsSignaturePolymorphic(using Context): Boolean = + maybeOwner == defn.MethodHandleClass + || maybeOwner == defn.VarHandleClass + + def originalSignaturePolymorphic(using Context): Denotation = + if containsSignaturePolymorphic && !isSignaturePolymorphic then + val d = owner.info.member(name) + if d.symbol.isSignaturePolymorphic then d else NoDenotation + else NoDenotation + def isInlineMethod(using Context): Boolean = isAllOf(InlineMethod, butNot = Accessor) @@ -2430,8 +2451,6 @@ object SymDenotations { ) if compiledNow.exists then compiledNow else - //val union = (d1: Set[AbstractFile], d2: Set[AbstractFile]) => d1.union(d2) - // !cc! need to break `u` out into separate definition, writing `_ union _` below gives an error val assocFiles = multi.aggregate(d => Set(d.symbol.associatedFile.nn), _ union _) if assocFiles.size == 1 then multi // they are all overloaded variants from the same file @@ -2441,13 +2460,13 @@ object SymDenotations { val youngest = assocFiles.filter(_.lastModified == lastModDate) val chosen = youngest.head def ambiguousFilesMsg(f: AbstractFile) = - em"""Toplevel definition $name is defined in - | $chosen - |and also in - | $f""" + i"""Toplevel definition $name is defined in + | $chosen + |and also in + | $f""" if youngest.size > 1 then - throw TypeError(i"""${ambiguousFilesMsg(youngest.tail.head)} - |One of these files should be removed from the classpath.""") + throw TypeError(em"""${ambiguousFilesMsg(youngest.tail.head)} + |One of these files should be removed from the classpath.""") // Warn if one of the older files comes from a different container. // In that case picking the youngest file is not necessarily what we want, @@ -2457,8 +2476,8 @@ object SymDenotations { try f.container == chosen.container catch case NonFatal(ex) => true if !ambiguityWarningIssued then for conflicting <- assocFiles.find(!sameContainer(_)) do - report.warning(i"""${ambiguousFilesMsg(conflicting.nn)} - |Keeping only the definition in $chosen""") + report.warning(em"""${ambiguousFilesMsg(conflicting.nn)} + |Keeping only the definition in $chosen""") ambiguityWarningIssued = true multi.filterWithPredicate(_.symbol.associatedFile == chosen) end dropStale diff --git a/tests/pos-with-compiler-cc/dotc/core/SymbolLoaders.scala b/tests/pos-with-compiler-cc/dotc/core/SymbolLoaders.scala index 5af45a016891..21b27f1667e5 100644 --- a/tests/pos-with-compiler-cc/dotc/core/SymbolLoaders.scala +++ b/tests/pos-with-compiler-cc/dotc/core/SymbolLoaders.scala @@ -89,8 +89,8 @@ object SymbolLoaders { return NoSymbol } else - throw new TypeError( - i"""$owner contains object and package with same name: $pname + throw TypeError( + em"""$owner contains object and package with same name: $pname |one of them needs to be removed from classpath""") newModuleSymbol(owner, pname, PackageCreationFlags, PackageCreationFlags, completer).entered @@ -335,8 +335,9 @@ abstract class SymbolLoader extends LazyType { self => if (ctx.debug) ex.printStackTrace() val msg = ex.getMessage() report.error( - if (msg == null) "i/o error while loading " + root.name - else "error while loading " + root.name + ",\n" + msg) + if msg == null then em"i/o error while loading ${root.name}" + else em"""error while loading ${root.name}, + |$msg""") } try { val start = System.currentTimeMillis diff --git a/tests/pos-with-compiler-cc/dotc/core/TypeComparer.scala b/tests/pos-with-compiler-cc/dotc/core/TypeComparer.scala index 95830fcf4b00..8e4bc9958596 100644 --- a/tests/pos-with-compiler-cc/dotc/core/TypeComparer.scala +++ b/tests/pos-with-compiler-cc/dotc/core/TypeComparer.scala @@ -419,16 +419,16 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling true } def compareTypeParamRef = - assumedTrue(tp1) || - tp2.match { - case tp2: TypeParamRef => constraint.isLess(tp1, tp2) - case _ => false - } || - isSubTypeWhenFrozen(bounds(tp1).hi.boxed, tp2) || { - if (canConstrain(tp1) && !approx.high) - addConstraint(tp1, tp2, fromBelow = false) && flagNothingBound - else thirdTry - } + assumedTrue(tp1) + || tp2.dealias.match + case tp2a: TypeParamRef => constraint.isLess(tp1, tp2a) + case tp2a: AndType => recur(tp1, tp2a) + case _ => false + || isSubTypeWhenFrozen(bounds(tp1).hi.boxed, tp2) + || (if canConstrain(tp1) && !approx.high then + addConstraint(tp1, tp2, fromBelow = false) && flagNothingBound + else thirdTry) + compareTypeParamRef case tp1: ThisType => val cls1 = tp1.cls @@ -586,7 +586,8 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling } def compareTypeParamRef(tp2: TypeParamRef): Boolean = - assumedTrue(tp2) || { + assumedTrue(tp2) + || { val alwaysTrue = // The following condition is carefully formulated to catch all cases // where the subtype relation is true without needing to add a constraint @@ -597,11 +598,13 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling // widening in `fourthTry` before adding to the constraint. if (frozenConstraint) recur(tp1, bounds(tp2).lo.boxed) else isSubTypeWhenFrozen(tp1, tp2) - alwaysTrue || { - if (canConstrain(tp2) && !approx.low) - addConstraint(tp2, tp1.widenExpr, fromBelow = true) - else fourthTry - } + alwaysTrue + || tp1.dealias.match + case tp1a: OrType => recur(tp1a, tp2) + case _ => false + || (if canConstrain(tp2) && !approx.low then + addConstraint(tp2, tp1.widenExpr, fromBelow = true) + else fourthTry) } def thirdTry: Boolean = tp2 match { @@ -3162,7 +3165,7 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) { tp case Nil => val casesText = MatchTypeTrace.noMatchesText(scrut, cases) - throw new TypeError(s"Match type reduction $casesText") + throw TypeError(em"Match type reduction $casesText") inFrozenConstraint { // Empty types break the basic assumption that if a scrutinee and a diff --git a/tests/pos-with-compiler-cc/dotc/core/TypeErrors.scala b/tests/pos-with-compiler-cc/dotc/core/TypeErrors.scala index 4d8aae319d27..f96637bcbd07 100644 --- a/tests/pos-with-compiler-cc/dotc/core/TypeErrors.scala +++ b/tests/pos-with-compiler-cc/dotc/core/TypeErrors.scala @@ -15,37 +15,44 @@ import ast.untpd import config.Printers.cyclicErrors import language.experimental.pureFunctions -class TypeError(msg: String) extends Exception(msg) { - def this() = this("") - final def toMessage(using Context): Message = - withMode(Mode.Printing)(produceMessage) - def produceMessage(using Context): Message = super.getMessage.nn.toMessage - override def getMessage: String = super.getMessage.nn -} - -class MalformedType(pre: Type, denot: Denotation, absMembers: Set[Name]) extends TypeError { - override def produceMessage(using Context): Message = - i"malformed type: $pre is not a legal prefix for $denot because it contains abstract type member${if (absMembers.size == 1) "" else "s"} ${absMembers.mkString(", ")}" - .toMessage -} - -class MissingType(pre: Type, name: Name) extends TypeError { +abstract class TypeError(using creationContext: Context) extends Exception(""), caps.Pure: + + /** Convert to message. This takes an additional Context, so that we + * use the context when the message is first produced, i.e. when the TypeError + * is handled. This makes a difference for CyclicErrors since we need to know + * the context where the completed symbol is referenced, but the creation + * context of the CyclicReference is the completion context for the symbol. + * See i2887b for a test case, where we want to see + * "recursive or overloaded method needs result type". + */ + def toMessage(using Context): Message + + /** Uses creationContext to produce the message */ + override def getMessage: String = toMessage.message + +object TypeError: + def apply(msg: Message)(using Context) = new TypeError: + def toMessage(using Context) = msg +end TypeError + +class MalformedType(pre: Type, denot: Denotation, absMembers: Set[Name])(using Context) extends TypeError: + def toMessage(using Context) = em"malformed type: $pre is not a legal prefix for $denot because it contains abstract type member${if (absMembers.size == 1) "" else "s"} ${absMembers.mkString(", ")}" + +class MissingType(pre: Type, name: Name)(using Context) extends TypeError: private def otherReason(pre: Type)(using Context): String = pre match { case pre: ThisType if pre.cls.givenSelfType.exists => i"\nor the self type of $pre might not contain all transitive dependencies" case _ => "" } - override def produceMessage(using Context): Message = { - if (ctx.debug) printStackTrace() - i"""cannot resolve reference to type $pre.$name - |the classfile defining the type might be missing from the classpath${otherReason(pre)}""" - .toMessage - } -} + override def toMessage(using Context): Message = + if ctx.debug then printStackTrace() + em"""cannot resolve reference to type $pre.$name + |the classfile defining the type might be missing from the classpath${otherReason(pre)}""" +end MissingType -class RecursionOverflow(val op: String, details: -> String, val previous: Throwable, val weight: Int) -extends TypeError { +class RecursionOverflow(val op: String, details: -> String, val previous: Throwable, val weight: Int)(using Context) +extends TypeError: def explanation: String = s"$op $details" @@ -72,19 +79,18 @@ extends TypeError { (rs.map(_.explanation): List[String]).mkString("\n ", "\n| ", "") } - override def produceMessage(using Context): Message = NoExplanation { + override def toMessage(using Context): Message = val mostCommon = recursions.groupBy(_.op).toList.maxBy(_._2.map(_.weight).sum)._2.reverse - s"""Recursion limit exceeded. - |Maybe there is an illegal cyclic reference? - |If that's not the case, you could also try to increase the stacksize using the -Xss JVM option. - |For the unprocessed stack trace, compile with -Yno-decode-stacktraces. - |A recurring operation is (inner to outer): - |${opsString(mostCommon)}""".stripMargin - } + em"""Recursion limit exceeded. + |Maybe there is an illegal cyclic reference? + |If that's not the case, you could also try to increase the stacksize using the -Xss JVM option. + |For the unprocessed stack trace, compile with -Yno-decode-stacktraces. + |A recurring operation is (inner to outer): + |${opsString(mostCommon).stripMargin}""" override def fillInStackTrace(): Throwable = this override def getStackTrace(): Array[StackTraceElement] = previous.getStackTrace().asInstanceOf -} +end RecursionOverflow /** Post-process exceptions that might result from StackOverflow to add * tracing information while unwalking the stack. @@ -112,10 +118,10 @@ object handleRecursive { * so it requires knowing denot already. * @param denot */ -class CyclicReference private (val denot: SymDenotation) extends TypeError { +class CyclicReference private (val denot: SymDenotation)(using Context) extends TypeError: var inImplicitSearch: Boolean = false - override def produceMessage(using Context): Message = { + override def toMessage(using Context): Message = val cycleSym = denot.symbol // cycleSym.flags would try completing denot and would fail, but here we can use flagsUNSAFE to detect flags @@ -152,19 +158,16 @@ class CyclicReference private (val denot: SymDenotation) extends TypeError { CyclicReferenceInvolving(denot) errorMsg(ctx) - } -} + end toMessage -object CyclicReference { - def apply(denot: SymDenotation)(using Context): CyclicReference = { +object CyclicReference: + def apply(denot: SymDenotation)(using Context): CyclicReference = val ex = new CyclicReference(denot) - if (!(ctx.mode is Mode.CheckCyclic) || ctx.settings.Ydebug.value) { + if !(ctx.mode is Mode.CheckCyclic) || ctx.settings.Ydebug.value then cyclicErrors.println(s"Cyclic reference involving! $denot") val sts = ex.getStackTrace.asInstanceOf[Array[StackTraceElement]] for (elem <- sts take 200) cyclicErrors.println(elem.toString) - } ex - } -} +end CyclicReference diff --git a/tests/pos-with-compiler-cc/dotc/core/TypeEval.scala b/tests/pos-with-compiler-cc/dotc/core/TypeEval.scala index 7ec0f12db3b6..b5684b07f181 100644 --- a/tests/pos-with-compiler-cc/dotc/core/TypeEval.scala +++ b/tests/pos-with-compiler-cc/dotc/core/TypeEval.scala @@ -91,7 +91,7 @@ object TypeEval: val result = try op catch case e: Throwable => - throw new TypeError(e.getMessage.nn) + throw TypeError(em"${e.getMessage.nn}") ConstantType(Constant(result)) def constantFold1[T](extractor: Type => Option[T], op: T => Any): Option[Type] = diff --git a/tests/pos-with-compiler-cc/dotc/core/TypeOps.scala b/tests/pos-with-compiler-cc/dotc/core/TypeOps.scala index 05ce8cefc285..b4917c2d42b9 100644 --- a/tests/pos-with-compiler-cc/dotc/core/TypeOps.scala +++ b/tests/pos-with-compiler-cc/dotc/core/TypeOps.scala @@ -226,16 +226,18 @@ object TypeOps: */ def orDominator(tp: Type)(using Context): Type = { - /** a faster version of cs1 intersect cs2 that treats bottom types correctly */ + /** a faster version of cs1 intersect cs2 */ def intersect(cs1: List[ClassSymbol], cs2: List[ClassSymbol]): List[ClassSymbol] = - if cs1.head == defn.NothingClass then cs2 - else if cs2.head == defn.NothingClass then cs1 - else if cs1.head == defn.NullClass && !ctx.explicitNulls && cs2.head.derivesFrom(defn.ObjectClass) then cs2 - else if cs2.head == defn.NullClass && !ctx.explicitNulls && cs1.head.derivesFrom(defn.ObjectClass) then cs1 - else - val cs2AsSet = new util.HashSet[ClassSymbol](128) - cs2.foreach(cs2AsSet += _) - cs1.filter(cs2AsSet.contains) + val cs2AsSet = BaseClassSet(cs2) + cs1.filter(cs2AsSet.contains) + + /** a version of Type#baseClasses that treats bottom types correctly */ + def orBaseClasses(tp: Type): List[ClassSymbol] = tp.stripTypeVar match + case OrType(tp1, tp2) => + if tp1.isBottomType && (tp1 frozen_<:< tp2) then orBaseClasses(tp2) + else if tp2.isBottomType && (tp2 frozen_<:< tp1) then orBaseClasses(tp1) + else intersect(orBaseClasses(tp1), orBaseClasses(tp2)) + case _ => tp.baseClasses /** The minimal set of classes in `cs` which derive all other classes in `cs` */ def dominators(cs: List[ClassSymbol], accu: List[ClassSymbol]): List[ClassSymbol] = (cs: @unchecked) match { @@ -370,7 +372,7 @@ object TypeOps: } // Step 3: Intersect base classes of both sides - val commonBaseClasses = tp.mapReduceOr(_.baseClasses)(intersect) + val commonBaseClasses = orBaseClasses(tp) val doms = dominators(commonBaseClasses, Nil) def baseTp(cls: ClassSymbol): Type = tp.baseType(cls).mapReduceOr(identity)(mergeRefinedOrApplied) diff --git a/tests/pos-with-compiler-cc/dotc/core/Types.scala b/tests/pos-with-compiler-cc/dotc/core/Types.scala index e34130a052ba..dcd8cd11f3ca 100644 --- a/tests/pos-with-compiler-cc/dotc/core/Types.scala +++ b/tests/pos-with-compiler-cc/dotc/core/Types.scala @@ -399,6 +399,10 @@ object Types { def isRepeatedParam(using Context): Boolean = typeSymbol eq defn.RepeatedParamClass + /** Is this a parameter type that allows implicit argument converson? */ + def isConvertibleParam(using Context): Boolean = + typeSymbol eq defn.IntoType + /** Is this the type of a method that has a repeated parameter type as * last parameter type? */ @@ -538,7 +542,7 @@ object Types { case tp: ClassInfo => tp.cls :: Nil case AndType(l, r) => - l.parentSymbols(include) | r.parentSymbols(include) + l.parentSymbols(include).setUnion(r.parentSymbols(include)) case OrType(l, r) => l.parentSymbols(include) intersect r.parentSymbols(include) // TODO does not conform to spec case _ => @@ -1866,6 +1870,11 @@ object Types { def dropRepeatedAnnot(using Context): Type = dropAnnot(defn.RepeatedAnnot) + /** A translation from types of original parameter ValDefs to the types + * of parameters in MethodTypes. + * Translates `Seq[T] @repeated` or `Array[T] @repeated` to `[T]`. + * That way, repeated arguments are made manifest without risk of dropped annotations. + */ def annotatedToRepeated(using Context): Type = this match { case tp @ ExprType(tp1) => tp.derivedExprType(tp1.annotatedToRepeated) @@ -2416,12 +2425,12 @@ object Types { } else { if (!ctx.reporter.errorsReported) - throw new TypeError( - i"""bad parameter reference $this at ${ctx.phase} - |the parameter is ${param.showLocated} but the prefix $prefix - |does not define any corresponding arguments. - |idx = $idx, args = $args%, %, - |constraint = ${ctx.typerState.constraint}""") + throw TypeError( + em"""bad parameter reference $this at ${ctx.phase} + |the parameter is ${param.showLocated} but the prefix $prefix + |does not define any corresponding arguments. + |idx = $idx, args = $args%, %, + |constraint = ${ctx.typerState.constraint}""") NoDenotation } } @@ -2449,6 +2458,8 @@ object Types { } private def checkDenot()(using Context) = {} + //if name.toString == "getConstructor" then + // println(i"set denot of $this to ${denot.info}, ${denot.getClass}, ${Phases.phaseOf(denot.validFor.lastPhaseId)} at ${ctx.phase}") private def checkSymAssign(sym: Symbol)(using Context) = { def selfTypeOf(sym: Symbol) = @@ -3951,27 +3962,48 @@ object Types { * and inline parameters: * - replace @repeated annotations on Seq or Array types by types * - add @inlineParam to inline parameters + * - add @erasedParam to erased parameters + * - wrap types of parameters that have an @allowConversions annotation with Into[_] */ - def fromSymbols(params: List[Symbol], resultType: Type)(using Context): MethodType = { - def translateInline(tp: Type): Type = tp match { - case ExprType(resType) => ExprType(AnnotatedType(resType, Annotation(defn.InlineParamAnnot))) - case _ => AnnotatedType(tp, Annotation(defn.InlineParamAnnot)) - } - def translateErased(tp: Type): Type = tp match { - case ExprType(resType) => ExprType(AnnotatedType(resType, Annotation(defn.ErasedParamAnnot))) - case _ => AnnotatedType(tp, Annotation(defn.ErasedParamAnnot)) - } - def paramInfo(param: Symbol) = { + def fromSymbols(params: List[Symbol], resultType: Type)(using Context): MethodType = + def addAnnotation(tp: Type, cls: ClassSymbol): Type = tp match + case ExprType(resType) => ExprType(addAnnotation(resType, cls)) + case _ => AnnotatedType(tp, Annotation(cls)) + + def wrapConvertible(tp: Type) = + AppliedType(defn.IntoType.typeRef, tp :: Nil) + + /** Add `Into[..] to the type itself and if it is a function type, to all its + * curried result type(s) as well. + */ + def addInto(tp: Type): Type = tp match + case tp @ AppliedType(tycon, args) if tycon.typeSymbol == defn.RepeatedParamClass => + tp.derivedAppliedType(tycon, addInto(args.head) :: Nil) + case tp @ AppliedType(tycon, args) if defn.isFunctionType(tp) => + wrapConvertible(tp.derivedAppliedType(tycon, args.init :+ addInto(args.last))) + case tp @ RefinedType(parent, rname, rinfo) if defn.isFunctionOrPolyType(tp) => + wrapConvertible(tp.derivedRefinedType(parent, rname, addInto(rinfo))) + case tp: MethodOrPoly => + tp.derivedLambdaType(resType = addInto(tp.resType)) + case ExprType(resType) => + ExprType(addInto(resType)) + case _ => + wrapConvertible(tp) + + def paramInfo(param: Symbol) = var paramType = param.info.annotatedToRepeated - if (param.is(Inline)) paramType = translateInline(paramType) - if (param.is(Erased)) paramType = translateErased(paramType) + if param.is(Inline) then + paramType = addAnnotation(paramType, defn.InlineParamAnnot) + if param.is(Erased) then + paramType = addAnnotation(paramType, defn.ErasedParamAnnot) + if param.hasAnnotation(defn.AllowConversionsAnnot) then + paramType = addInto(paramType) paramType - } apply(params.map(_.name.asTermName))( tl => params.map(p => tl.integrate(params, paramInfo(p))), tl => tl.integrate(params, resultType)) - } + end fromSymbols final def apply(paramNames: List[TermName])(paramInfosExp: MethodType => List[Type], resultTypeExp: MethodType => Type)(using Context): MethodType = checkValid(unique(new CachedMethodType(paramNames)(paramInfosExp, resultTypeExp, self))) @@ -5296,7 +5328,12 @@ object Types { abstract class FlexType extends UncachedGroundType with ValueType abstract class ErrorType extends FlexType { + + /** An explanation of the cause of the failure */ def msg(using Context): Message + + /** An explanation of the cause of the failure as a string */ + def explanation(using Context): String = msg.message } object ErrorType: @@ -5304,18 +5341,16 @@ object Types { val et = new PreviousErrorType ctx.base.errorTypeMsg(et) = m et - def apply(s: -> String)(using Context): ErrorType = - apply(s.toMessage) end ErrorType class PreviousErrorType extends ErrorType: def msg(using Context): Message = ctx.base.errorTypeMsg.get(this) match case Some(m) => m - case None => "error message from previous run no longer available".toMessage + case None => em"error message from previous run no longer available" object UnspecifiedErrorType extends ErrorType { - override def msg(using Context): Message = "unspecified error".toMessage + override def msg(using Context): Message = em"unspecified error" } /* Type used to track Select nodes that could not resolve a member and their qualifier is a scala.Dynamic. */ @@ -5502,6 +5537,14 @@ object Types { stop == StopAt.Static && tp.currentSymbol.isStatic && isStaticPrefix(tp.prefix) || stop == StopAt.Package && tp.currentSymbol.is(Package) } + + /** The type parameters of the constructor of this applied type. + * Overridden in OrderingConstraint's ConstraintAwareTraversal to take account + * of instantiations in the constraint that are not yet propagated to the + * instance types of type variables. + */ + protected def tyconTypeParams(tp: AppliedType)(using Context): List[ParamInfo] = + tp.tyconTypeParams end VariantTraversal /** A supertrait for some typemaps that are bijections. Used for capture checking. @@ -5609,17 +5652,11 @@ object Types { case tp: NamedType => if stopBecauseStaticOrLocal(tp) then tp else - val prefix1 = atVariance(variance max 0)(this(tp.prefix)) - // A prefix is never contravariant. Even if say `p.A` is used in a contravariant - // context, we cannot assume contravariance for `p` because `p`'s lower - // bound might not have a binding for `A` (e.g. the lower bound could be `Nothing`). - // By contrast, covariance does translate to the prefix, since we have that - // if `p <: q` then `p.A <: q.A`, and well-formedness requires that `A` is a member - // of `p`'s upper bound. + val prefix1 = atVariance(variance max 0)(this(tp.prefix)) // see comment of TypeAccumulator's applyToPrefix derivedSelect(tp, prefix1) case tp: AppliedType => - derivedAppliedType(tp, this(tp.tycon), mapArgs(tp.args, tp.tyconTypeParams)) + derivedAppliedType(tp, this(tp.tycon), mapArgs(tp.args, tyconTypeParams(tp))) case tp: LambdaType => mapOverLambda(tp) @@ -5946,7 +5983,7 @@ object Types { case nil => true } - if (distributeArgs(args, tp.tyconTypeParams)) + if (distributeArgs(args, tyconTypeParams(tp))) range(tp.derivedAppliedType(tycon, loBuf.toList), tp.derivedAppliedType(tycon, hiBuf.toList)) else if tycon.isLambdaSub || args.exists(isRangeOfNonTermTypes) then @@ -6068,8 +6105,17 @@ object Types { protected def applyToAnnot(x: T, annot: Annotation): T = x // don't go into annotations - protected final def applyToPrefix(x: T, tp: NamedType): T = - atVariance(variance max 0)(this(x, tp.prefix)) // see remark on NamedType case in TypeMap + /** A prefix is never contravariant. Even if say `p.A` is used in a contravariant + * context, we cannot assume contravariance for `p` because `p`'s lower + * bound might not have a binding for `A`, since the lower bound could be `Nothing`. + * By contrast, covariance does translate to the prefix, since we have that + * if `p <: q` then `p.A <: q.A`, and well-formedness requires that `A` is a member + * of `p`'s upper bound. + * Overridden in OrderingConstraint's ConstraintAwareTraversal, where a + * more relaxed scheme is used. + */ + protected def applyToPrefix(x: T, tp: NamedType): T = + atVariance(variance max 0)(this(x, tp.prefix)) def foldOver(x: T, tp: Type): T = { record(s"foldOver $getClass") @@ -6092,7 +6138,7 @@ object Types { } foldArgs(acc, tparams.tail, args.tail) } - foldArgs(this(x, tycon), tp.tyconTypeParams, args) + foldArgs(this(x, tycon), tyconTypeParams(tp), args) case _: BoundType | _: ThisType => x diff --git a/tests/pos-with-compiler-cc/dotc/core/classfile/ClassfileParser.scala b/tests/pos-with-compiler-cc/dotc/core/classfile/ClassfileParser.scala index 1cb4970265a7..af8694f40c9a 100644 --- a/tests/pos-with-compiler-cc/dotc/core/classfile/ClassfileParser.scala +++ b/tests/pos-with-compiler-cc/dotc/core/classfile/ClassfileParser.scala @@ -327,7 +327,7 @@ class ClassfileParser( if (isEnum) { val enumClass = sym.owner.linkedClass if (!enumClass.exists) - report.warning(s"no linked class for java enum $sym in ${sym.owner}. A referencing class file might be missing an InnerClasses entry.") + report.warning(em"no linked class for java enum $sym in ${sym.owner}. A referencing class file might be missing an InnerClasses entry.") else { if (!enumClass.is(Flags.Sealed)) enumClass.setFlag(Flags.AbstractSealed) enumClass.addAnnotation(Annotation.Child(sym, NoSpan)) @@ -657,7 +657,7 @@ class ClassfileParser( case tp: TypeRef if tp.denot.infoOrCompleter.isInstanceOf[StubInfo] => // Silently ignore missing annotation classes like javac if ctx.debug then - report.warning(i"Error while parsing annotations in ${classfile}: annotation class $tp not present on classpath") + report.warning(em"Error while parsing annotations in ${classfile}: annotation class $tp not present on classpath") None case _ => if (hasError || skip) None @@ -672,7 +672,7 @@ class ClassfileParser( // the classpath would *not* end up here. A class not found is signaled // with a `FatalError` exception, handled above. Here you'd end up after a NPE (for example), // and that should never be swallowed silently. - report.warning("Caught: " + ex + " while parsing annotations in " + classfile) + report.warning(em"Caught: $ex while parsing annotations in $classfile") if (ctx.debug) ex.printStackTrace() None // ignore malformed annotations @@ -754,7 +754,7 @@ class ClassfileParser( case tpnme.ConstantValueATTR => val c = pool.getConstant(in.nextChar) if (c ne null) res.constant = c - else report.warning(s"Invalid constant in attribute of ${sym.showLocated} while parsing ${classfile}") + else report.warning(em"Invalid constant in attribute of ${sym.showLocated} while parsing ${classfile}") case tpnme.MethodParametersATTR => val paramCount = in.nextByte @@ -968,7 +968,7 @@ class ClassfileParser( } } else { - report.error(s"Could not find $path in ${classfile.underlyingSource}") + report.error(em"Could not find $path in ${classfile.underlyingSource}") Array.empty } case _ => @@ -976,7 +976,7 @@ class ClassfileParser( val name = classfile.name.stripSuffix(".class") + ".tasty" val tastyFileOrNull = dir.lookupName(name, false) if (tastyFileOrNull == null) { - report.error(s"Could not find TASTY file $name under $dir") + report.error(em"Could not find TASTY file $name under $dir") Array.empty } else tastyFileOrNull.toByteArray diff --git a/tests/pos-with-compiler-cc/dotc/core/tasty/PositionPickler.scala b/tests/pos-with-compiler-cc/dotc/core/tasty/PositionPickler.scala index ad0c051e1b7b..e8946c8b4037 100644 --- a/tests/pos-with-compiler-cc/dotc/core/tasty/PositionPickler.scala +++ b/tests/pos-with-compiler-cc/dotc/core/tasty/PositionPickler.scala @@ -14,6 +14,7 @@ import core._ import Annotations._, Decorators._ import collection.mutable import util.Spans._ +import reporting.Message class PositionPickler( pickler: TastyPickler, @@ -33,7 +34,7 @@ class PositionPickler( (addrDelta << 3) | (toInt(hasStartDelta) << 2) | (toInt(hasEndDelta) << 1) | toInt(hasPoint) } - def picklePositions(source: SourceFile, roots: List[Tree], warnings: mutable.ListBuffer[String]): Unit = { + def picklePositions(source: SourceFile, roots: List[Tree], warnings: mutable.ListBuffer[Message]): Unit = { /** Pickle the number of lines followed by the length of each line */ def pickleLineOffsets(): Unit = { val content = source.content() diff --git a/tests/pos-with-compiler-cc/dotc/core/tasty/TreePickler.scala b/tests/pos-with-compiler-cc/dotc/core/tasty/TreePickler.scala index 34c22439a932..86e5ba228cff 100644 --- a/tests/pos-with-compiler-cc/dotc/core/tasty/TreePickler.scala +++ b/tests/pos-with-compiler-cc/dotc/core/tasty/TreePickler.scala @@ -207,7 +207,7 @@ class TreePickler(pickler: TastyPickler) { else if (tpe.prefix == NoPrefix) { writeByte(if (tpe.isType) TYPEREFdirect else TERMREFdirect) if Config.checkLevelsOnConstraints && !symRefs.contains(sym) && !sym.isPatternBound && !sym.hasAnnotation(defn.QuotedRuntimePatterns_patternTypeAnnot) then - report.error(i"pickling reference to as yet undefined $tpe with symbol ${sym}", sym.srcPos) + report.error(em"pickling reference to as yet undefined $tpe with symbol ${sym}", sym.srcPos) pickleSymRef(sym) } else tpe.designator match { @@ -426,6 +426,13 @@ class TreePickler(pickler: TastyPickler) { writeByte(THROW) pickleTree(args.head) } + else if fun.symbol.originalSignaturePolymorphic.exists then + writeByte(APPLYsigpoly) + withLength { + pickleTree(fun) + pickleType(fun.tpe.widenTermRefExpr, richTypes = true) // this widens to a MethodType, so need richTypes + args.foreach(pickleTree) + } else { writeByte(APPLY) withLength { @@ -451,7 +458,7 @@ class TreePickler(pickler: TastyPickler) { withLength { pickleTree(qual); if (!mix.isEmpty) { - // mixinType being a TypeRef when mix is non-empty is enforced by TreeChecker#checkSuper + // mixinType being a TypeRef when mix is non-empty is enforced by TreeChecker#checkSuper val SuperType(_, mixinType: TypeRef) = tree.tpe: @unchecked pickleTree(mix.withType(mixinType)) } diff --git a/tests/pos-with-compiler-cc/dotc/core/tasty/TreeUnpickler.scala b/tests/pos-with-compiler-cc/dotc/core/tasty/TreeUnpickler.scala index 69bd0d95ba3a..6f4ee45a9cd5 100644 --- a/tests/pos-with-compiler-cc/dotc/core/tasty/TreeUnpickler.scala +++ b/tests/pos-with-compiler-cc/dotc/core/tasty/TreeUnpickler.scala @@ -1237,6 +1237,12 @@ class TreeUnpickler(reader: TastyReader, else tpd.Apply(fn, args) case TYPEAPPLY => tpd.TypeApply(readTerm(), until(end)(readTpt())) + case APPLYsigpoly => + val fn = readTerm() + val methType = readType() + val args = until(end)(readTerm()) + val fun2 = typer.Applications.retypeSignaturePolymorphicFn(fn, methType) + tpd.Apply(fun2, args) case TYPED => val expr = readTerm() val tpt = readTpt() diff --git a/tests/pos-with-compiler-cc/dotc/core/unpickleScala2/Scala2Erasure.scala b/tests/pos-with-compiler-cc/dotc/core/unpickleScala2/Scala2Erasure.scala index f2d25d0f34b5..cc2d7dd7ee56 100644 --- a/tests/pos-with-compiler-cc/dotc/core/unpickleScala2/Scala2Erasure.scala +++ b/tests/pos-with-compiler-cc/dotc/core/unpickleScala2/Scala2Erasure.scala @@ -39,9 +39,9 @@ object Scala2Erasure: case RefinedType(parent, _, _) => checkSupported(parent) case AnnotatedType(parent, _) if parent.dealias.isInstanceOf[Scala2RefinedType] => - throw new TypeError(i"Unsupported Scala 2 type: Component $parent of intersection is annotated.") + throw TypeError(em"Unsupported Scala 2 type: Component $parent of intersection is annotated.") case tp @ TypeRef(prefix, _) if !tp.symbol.exists && prefix.dealias.isInstanceOf[Scala2RefinedType] => - throw new TypeError(i"Unsupported Scala 2 type: Prefix $prefix of intersection component is an intersection or refinement.") + throw TypeError(em"Unsupported Scala 2 type: Prefix $prefix of intersection component is an intersection or refinement.") case _ => /** A type that would be represented as a RefinedType in Scala 2. diff --git a/tests/pos-with-compiler-cc/dotc/fromtasty/ReadTasty.scala b/tests/pos-with-compiler-cc/dotc/fromtasty/ReadTasty.scala index 864f5277bff3..86ae99b3e0f9 100644 --- a/tests/pos-with-compiler-cc/dotc/fromtasty/ReadTasty.scala +++ b/tests/pos-with-compiler-cc/dotc/fromtasty/ReadTasty.scala @@ -29,7 +29,7 @@ class ReadTasty extends Phase { val className = unit.className.toTypeName def cannotUnpickle(reason: String): None.type = { - report.error(s"class $className cannot be unpickled because $reason") + report.error(em"class $className cannot be unpickled because $reason") None } diff --git a/tests/pos-with-compiler-cc/dotc/fromtasty/TASTYRun.scala b/tests/pos-with-compiler-cc/dotc/fromtasty/TASTYRun.scala index 04c65a3d3882..fb0abe3332ed 100644 --- a/tests/pos-with-compiler-cc/dotc/fromtasty/TASTYRun.scala +++ b/tests/pos-with-compiler-cc/dotc/fromtasty/TASTYRun.scala @@ -6,6 +6,7 @@ import scala.language.unsafeNulls import io.{JarArchive, AbstractFile, Path} import core.Contexts._ +import core.Decorators.em import java.io.File class TASTYRun(comp: Compiler, ictx: Context) extends Run(comp, ictx) { @@ -27,7 +28,7 @@ class TASTYRun(comp: Compiler, ictx: Context) extends Run(comp, ictx) { .toList case "tasty" => TastyFileUtil.getClassName(file) case _ => - report.error(s"File extension is not `tasty` or `jar`: ${file.path}") + report.error(em"File extension is not `tasty` or `jar`: ${file.path}") Nil } classNames.map(new TASTYCompilationUnit(_)) diff --git a/tests/pos-with-compiler-cc/dotc/inlines/InlineReducer.scala b/tests/pos-with-compiler-cc/dotc/inlines/InlineReducer.scala index 460d0a61c252..b85454b8ba35 100644 --- a/tests/pos-with-compiler-cc/dotc/inlines/InlineReducer.scala +++ b/tests/pos-with-compiler-cc/dotc/inlines/InlineReducer.scala @@ -158,35 +158,46 @@ class InlineReducer(inliner: Inliner)(using Context): * * where `def` is used for call-by-name parameters. However, we shortcut any NoPrefix * refs among the ei's directly without creating an intermediate binding. + * + * This variant of beta-reduction preserves the integrity of `Inlined` tree nodes. */ def betaReduce(tree: Tree)(using Context): Tree = tree match { - case Apply(Select(cl @ closureDef(ddef), nme.apply), args) if defn.isFunctionType(cl.tpe) => - // closureDef also returns a result for closures wrapped in Inlined nodes. - // These need to be preserved. - def recur(cl: Tree): Tree = cl match - case Inlined(call, bindings, expr) => - cpy.Inlined(cl)(call, bindings, recur(expr)) - case _ => ddef.tpe.widen match - case mt: MethodType if ddef.paramss.head.length == args.length => - val bindingsBuf = new DefBuffer - val argSyms = mt.paramNames.lazyZip(mt.paramInfos).lazyZip(args).map { (name, paramtp, arg) => - arg.tpe.dealias match { - case ref @ TermRef(NoPrefix, _) => ref.symbol - case _ => - paramBindingDef(name, paramtp, arg, bindingsBuf)( - using ctx.withSource(cl.source) - ).symbol + case Apply(Select(cl, nme.apply), args) if defn.isFunctionType(cl.tpe) => + val bindingsBuf = new DefBuffer + def recur(cl: Tree): Option[Tree] = cl match + case Block((ddef : DefDef) :: Nil, closure: Closure) if ddef.symbol == closure.meth.symbol => + ddef.tpe.widen match + case mt: MethodType if ddef.paramss.head.length == args.length => + val argSyms = mt.paramNames.lazyZip(mt.paramInfos).lazyZip(args).map { (name, paramtp, arg) => + arg.tpe.dealias match { + case ref @ TermRef(NoPrefix, _) => ref.symbol + case _ => + paramBindingDef(name, paramtp, arg, bindingsBuf)( + using ctx.withSource(cl.source) + ).symbol + } } - } - val expander = new TreeTypeMap( - oldOwners = ddef.symbol :: Nil, - newOwners = ctx.owner :: Nil, - substFrom = ddef.paramss.head.map(_.symbol), - substTo = argSyms) - Block(bindingsBuf.toList, expander.transform(ddef.rhs)).withSpan(tree.span) - case _ => tree - recur(cl) - case _ => tree + val expander = new TreeTypeMap( + oldOwners = ddef.symbol :: Nil, + newOwners = ctx.owner :: Nil, + substFrom = ddef.paramss.head.map(_.symbol), + substTo = argSyms) + Some(expander.transform(ddef.rhs)) + case _ => None + case Block(stats, expr) if stats.forall(isPureBinding) => + recur(expr).map(cpy.Block(cl)(stats, _)) + case Inlined(call, bindings, expr) if bindings.forall(isPureBinding) => + recur(expr).map(cpy.Inlined(cl)(call, bindings, _)) + case Typed(expr, tpt) => + recur(expr) + case _ => None + recur(cl) match + case Some(reduced) => + Block(bindingsBuf.toList, reduced).withSpan(tree.span) + case None => + tree + case _ => + tree } /** The result type of reducing a match. It consists optionally of a list of bindings @@ -281,7 +292,7 @@ class InlineReducer(inliner: Inliner)(using Context): // Test case is pos-macros/i15971 val tptBinds = getBinds(Set.empty[TypeSymbol], tpt) val binds: Set[TypeSymbol] = pat match { - case UnApply(TypeApply(_, tpts), _, _) => + case UnApply(TypeApply(_, tpts), _, _) => getBinds(Set.empty[TypeSymbol], tpts) ++ tptBinds case _ => tptBinds } diff --git a/tests/pos-with-compiler-cc/dotc/inlines/Inliner.scala b/tests/pos-with-compiler-cc/dotc/inlines/Inliner.scala index a23af052ca24..d59fbad670ca 100644 --- a/tests/pos-with-compiler-cc/dotc/inlines/Inliner.scala +++ b/tests/pos-with-compiler-cc/dotc/inlines/Inliner.scala @@ -254,7 +254,7 @@ class Inliner(val call: tpd.Tree)(using Context): computeParamBindings(tp.resultType, targs.drop(tp.paramNames.length), argss, formalss, buf) case tp: MethodType => if argss.isEmpty then - report.error(i"missing arguments for inline method $inlinedMethod", call.srcPos) + report.error(em"missing arguments for inline method $inlinedMethod", call.srcPos) false else tp.paramNames.lazyZip(formalss.head).lazyZip(argss.head).foreach { (name, formal, arg) => @@ -617,8 +617,8 @@ class Inliner(val call: tpd.Tree)(using Context): def issueError() = callValueArgss match { case (msgArg :: Nil) :: Nil => val message = msgArg.tpe match { - case ConstantType(Constant(msg: String)) => msg - case _ => s"A literal string is expected as an argument to `compiletime.error`. Got ${msgArg.show}" + case ConstantType(Constant(msg: String)) => msg.toMessage + case _ => em"A literal string is expected as an argument to `compiletime.error`. Got $msgArg" } // Usually `error` is called from within a rewrite method. In this // case we need to report the error at the point of the outermost enclosing inline diff --git a/tests/pos-with-compiler-cc/dotc/inlines/Inlines.scala b/tests/pos-with-compiler-cc/dotc/inlines/Inlines.scala index 8be23b932e98..9408ece0d4ac 100644 --- a/tests/pos-with-compiler-cc/dotc/inlines/Inlines.scala +++ b/tests/pos-with-compiler-cc/dotc/inlines/Inlines.scala @@ -85,7 +85,10 @@ object Inlines: if (tree.symbol == defn.CompiletimeTesting_typeChecks) return Intrinsics.typeChecks(tree) if (tree.symbol == defn.CompiletimeTesting_typeCheckErrors) return Intrinsics.typeCheckErrors(tree) - CrossVersionChecks.checkExperimentalRef(tree.symbol, tree.srcPos) + if ctx.isAfterTyper then + // During typer we wait with cross version checks until PostTyper, in order + // not to provoke cyclic references. See i16116 for a test case. + CrossVersionChecks.checkExperimentalRef(tree.symbol, tree.srcPos) if tree.symbol.isConstructor then return tree // error already reported for the inline constructor definition @@ -153,9 +156,9 @@ object Inlines: else ("successive inlines", ctx.settings.XmaxInlines) errorTree( tree, - i"""|Maximal number of $reason (${setting.value}) exceeded, - |Maybe this is caused by a recursive inline method? - |You can use ${setting.name} to change the limit.""".toMessage, + em"""|Maximal number of $reason (${setting.value}) exceeded, + |Maybe this is caused by a recursive inline method? + |You can use ${setting.name} to change the limit.""", (tree :: enclosingInlineds).last.srcPos ) if ctx.base.stopInlining && enclosingInlineds.isEmpty then @@ -439,8 +442,7 @@ object Inlines: val evidence = evTyper.inferImplicitArg(tpt.tpe, tpt.span) evidence.tpe match case fail: Implicits.SearchFailureType => - val msg = evTyper.missingArgMsg(evidence, tpt.tpe, "") - errorTree(call, em"$msg") + errorTree(call, evTyper.missingArgMsg(evidence, tpt.tpe, "")) case _ => evidence } diff --git a/tests/pos-with-compiler-cc/dotc/inlines/PrepareInlineable.scala b/tests/pos-with-compiler-cc/dotc/inlines/PrepareInlineable.scala index db52712c39e2..293aa61abdb1 100644 --- a/tests/pos-with-compiler-cc/dotc/inlines/PrepareInlineable.scala +++ b/tests/pos-with-compiler-cc/dotc/inlines/PrepareInlineable.scala @@ -285,7 +285,7 @@ object PrepareInlineable { private def checkInlineMethod(inlined: Symbol, body: Tree)(using Context): body.type = { if Inlines.inInlineMethod(using ctx.outer) then - report.error(ex"Implementation restriction: nested inline methods are not supported", inlined.srcPos) + report.error(em"Implementation restriction: nested inline methods are not supported", inlined.srcPos) if (inlined.is(Macro) && !ctx.isAfterTyper) { diff --git a/tests/pos-with-compiler-cc/dotc/interactive/Completion.scala b/tests/pos-with-compiler-cc/dotc/interactive/Completion.scala index 414d406e870a..8ccdaae41371 100644 --- a/tests/pos-with-compiler-cc/dotc/interactive/Completion.scala +++ b/tests/pos-with-compiler-cc/dotc/interactive/Completion.scala @@ -16,6 +16,7 @@ import dotty.tools.dotc.core.Symbols.{NoSymbol, Symbol, defn, newSymbol} import dotty.tools.dotc.core.StdNames.nme import dotty.tools.dotc.core.SymDenotations.SymDenotation import dotty.tools.dotc.core.TypeError +import dotty.tools.dotc.core.Phases import dotty.tools.dotc.core.Types.{AppliedType, ExprType, MethodOrPoly, NameFilter, NoType, RefinedType, TermRef, Type, TypeProxy} import dotty.tools.dotc.parsing.Tokens import dotty.tools.dotc.util.Chars @@ -45,7 +46,7 @@ object Completion { */ def completions(pos: SourcePosition)(using Context): (Int, List[Completion]) = { val path = Interactive.pathTo(ctx.compilationUnit.tpdTree, pos.span) - computeCompletions(pos, path)(using Interactive.contextOfPath(path)) + computeCompletions(pos, path)(using Interactive.contextOfPath(path).withPhase(Phases.typerPhase)) } /** diff --git a/tests/pos-with-compiler-cc/dotc/parsing/JavaParsers.scala b/tests/pos-with-compiler-cc/dotc/parsing/JavaParsers.scala index 183845fcf3ec..daeebcbcc17c 100644 --- a/tests/pos-with-compiler-cc/dotc/parsing/JavaParsers.scala +++ b/tests/pos-with-compiler-cc/dotc/parsing/JavaParsers.scala @@ -71,10 +71,10 @@ object JavaParsers { } } - def syntaxError(msg: String, skipIt: Boolean): Unit = + def syntaxError(msg: Message, skipIt: Boolean): Unit = syntaxError(in.offset, msg, skipIt) - def syntaxError(offset: Int, msg: String, skipIt: Boolean): Unit = { + def syntaxError(offset: Int, msg: Message, skipIt: Boolean): Unit = { if (offset > lastErrorOffset) { syntaxError(msg, offset) // no more errors on this token. @@ -178,9 +178,7 @@ object JavaParsers { if (in.token != token) { val offsetToReport = in.offset val msg = - tokenString(token) + " expected but " + - tokenString(in.token) + " found." - + em"${tokenString(token)} expected but ${tokenString(in.token)} found." syntaxError(offsetToReport, msg, skipIt = true) } if (in.token == token) in.nextToken() @@ -271,7 +269,7 @@ object JavaParsers { case FLOAT => in.nextToken(); TypeTree(FloatType) case DOUBLE => in.nextToken(); TypeTree(DoubleType) case BOOLEAN => in.nextToken(); TypeTree(BooleanType) - case _ => syntaxError("illegal start of type", skipIt = true); errorTypeTree + case _ => syntaxError(em"illegal start of type", skipIt = true); errorTypeTree } } @@ -762,7 +760,7 @@ object JavaParsers { accept(SEMI) val names = buf.toList if (names.length < 2) { - syntaxError(start, "illegal import", skipIt = false) + syntaxError(start, em"illegal import", skipIt = false) List() } else { @@ -954,7 +952,7 @@ object JavaParsers { case INTERFACE => interfaceDecl(start, mods) case AT => annotationDecl(start, mods) case CLASS => classDecl(start, mods) - case _ => in.nextToken(); syntaxError("illegal start of type declaration", skipIt = true); List(errorTypeTree) + case _ => in.nextToken(); syntaxError(em"illegal start of type declaration", skipIt = true); List(errorTypeTree) } def tryConstant: Option[Constant] = { diff --git a/tests/pos-with-compiler-cc/dotc/parsing/JavaScanners.scala b/tests/pos-with-compiler-cc/dotc/parsing/JavaScanners.scala index 1be8bdae6bd1..d21d4b85b5df 100644 --- a/tests/pos-with-compiler-cc/dotc/parsing/JavaScanners.scala +++ b/tests/pos-with-compiler-cc/dotc/parsing/JavaScanners.scala @@ -10,6 +10,7 @@ import JavaTokens._ import scala.annotation.{switch, tailrec} import util.Chars._ import PartialFunction.cond +import core.Decorators.em object JavaScanners { @@ -108,7 +109,7 @@ object JavaScanners { setStrVal() nextChar() else - error("unclosed string literal") + error(em"unclosed string literal") else nextChar() if ch != '\"' then // "" empty string literal @@ -127,7 +128,7 @@ object JavaScanners { setStrVal() } else - error("unclosed character literal") + error(em"unclosed character literal") case '=' => token = EQUALS @@ -298,7 +299,7 @@ object JavaScanners { nextChar() token = DOTDOTDOT } - else error("`.` character expected") + else error(em"`.` character expected") } case ';' => @@ -336,7 +337,7 @@ object JavaScanners { case SU => if (isAtEnd) token = EOF else { - error("illegal character") + error(em"illegal character") nextChar() } @@ -347,7 +348,7 @@ object JavaScanners { getIdentRest() } else { - error("illegal character: " + ch.toInt) + error(em"illegal character: ${ch.toInt}") nextChar() } } @@ -360,7 +361,7 @@ object JavaScanners { case _ => nextChar(); skipLineComment() } @tailrec def skipJavaComment(): Unit = ch match { - case SU => incompleteInputError("unclosed comment") + case SU => incompleteInputError(em"unclosed comment") case '*' => nextChar(); if (ch == '/') nextChar() else skipJavaComment() case _ => nextChar(); skipJavaComment() } @@ -480,7 +481,7 @@ object JavaScanners { nextChar() } if (ch != LF && ch != CR) { // CR-LF is already normalized into LF by `JavaCharArrayReader` - error("illegal text block open delimiter sequence, missing line terminator") + error(em"illegal text block open delimiter sequence, missing line terminator") return } nextChar() @@ -529,7 +530,7 @@ object JavaScanners { // Bail out if the block never did have an end if (!blockClosed) { - error("unclosed text block") + error(em"unclosed text block") return } @@ -642,14 +643,14 @@ object JavaScanners { while (i < len) { val d = digit2int(strVal.charAt(i), base) if (d < 0) { - error("malformed integer number") + error(em"malformed integer number") return 0 } if (value < 0 || limit / (base / divider) < value || limit - (d / divider) < value * (base / divider) && !(negated && limit == value * base - 1 + d)) { - error("integer number too large") + error(em"integer number too large") return 0 } value = value * base + d @@ -666,11 +667,11 @@ object JavaScanners { try { val value: Double = java.lang.Double.valueOf(strVal.toString).nn.doubleValue() if (value > limit) - error("floating point number too large") + error(em"floating point number too large") if (negated) -value else value } catch { case _: NumberFormatException => - error("malformed floating point number") + error(em"malformed floating point number") 0.0 } } diff --git a/tests/pos-with-compiler-cc/dotc/parsing/Parsers.scala b/tests/pos-with-compiler-cc/dotc/parsing/Parsers.scala index 0b1a2bdcd679..461a5e294198 100644 --- a/tests/pos-with-compiler-cc/dotc/parsing/Parsers.scala +++ b/tests/pos-with-compiler-cc/dotc/parsing/Parsers.scala @@ -33,7 +33,6 @@ import config.Feature import config.Feature.{sourceVersion, migrateTo3, globalOnlyImports} import config.SourceVersion._ import config.SourceVersion -import language.experimental.pureFunctions object Parsers { @@ -144,21 +143,12 @@ object Parsers { syntaxError(msg, Span(offset, offset + length)) lastErrorOffset = in.offset - def syntaxError(msg: -> String, offset: Int): Unit = - syntaxError(msg.toMessage, offset) - - def syntaxError(msg: -> String): Unit = - syntaxError(msg, in.offset) - /** Unconditionally issue an error at given span, without * updating lastErrorOffset. */ def syntaxError(msg: Message, span: Span): Unit = report.error(msg, source.atSpan(span)) - def syntaxError(msg: -> String, span: Span): Unit = - syntaxError(msg.toMessage, span) - def unimplementedExpr(using Context): Select = Select(scalaDot(nme.Predef), nme.???) } @@ -289,9 +279,6 @@ object Parsers { syntaxError(msg, offset) skip() - def syntaxErrorOrIncomplete(msg: -> String): Unit = - syntaxErrorOrIncomplete(msg.toMessage, in.offset) - def syntaxErrorOrIncomplete(msg: Message, span: Span): Unit = if in.token == EOF then incompleteInputError(msg) @@ -347,7 +334,7 @@ object Parsers { in.nextToken() recur(true, endSeen) else if in.token == END then - if endSeen then syntaxError("duplicate end marker") + if endSeen then syntaxError(em"duplicate end marker") checkEndMarker(stats) recur(sepSeen, endSeen = true) else if isStatSeqEnd || in.token == altEnd then @@ -359,7 +346,7 @@ object Parsers { val statFollows = mustStartStatTokens.contains(found) syntaxError( if noPrevStat then IllegalStartOfStatement(what, isModifier, statFollows) - else i"end of $what expected but ${showToken(found)} found".toMessage) + else em"end of $what expected but ${showToken(found)} found") if mustStartStatTokens.contains(found) then false // it's a statement that might be legal in an outer context else @@ -461,7 +448,7 @@ object Parsers { */ def convertToParam(tree: Tree, mods: Modifiers): ValDef = def fail() = - syntaxError(s"not a legal formal parameter for a function literal", tree.span) + syntaxError(em"not a legal formal parameter for a function literal", tree.span) makeParameter(nme.ERROR, tree, mods) tree match case param: ValDef => @@ -619,11 +606,11 @@ object Parsers { if in.isNewLine && !(nextIndentWidth < startIndentWidth) then warning( if startIndentWidth <= nextIndentWidth then - i"""Line is indented too far to the right, or a `{` is missing before: - | - |${t.tryToShow}""".toMessage + em"""Line is indented too far to the right, or a `{` is missing before: + | + |${t.tryToShow}""" else - in.spaceTabMismatchMsg(startIndentWidth, nextIndentWidth).toMessage, + in.spaceTabMismatchMsg(startIndentWidth, nextIndentWidth), in.next.offset ) t @@ -636,7 +623,7 @@ object Parsers { if in.isNewLine then val nextIndentWidth = in.indentWidth(in.next.offset) if in.currentRegion.indentWidth < nextIndentWidth then - warning(i"Line is indented too far to the right, or a `{` or `:` is missing".toMessage, in.next.offset) + warning(em"Line is indented too far to the right, or a `{` or `:` is missing", in.next.offset) /* -------- REWRITES ----------------------------------------------------------- */ @@ -1082,7 +1069,7 @@ object Parsers { val name = in.name if name == nme.CONSTRUCTOR || name == nme.STATIC_CONSTRUCTOR then report.error( - i"""Illegal backquoted identifier: `` and `` are forbidden""", + em"""Illegal backquoted identifier: `` and `` are forbidden""", in.sourcePos()) in.nextToken() name @@ -1235,7 +1222,7 @@ object Parsers { null } catch { - case ex: FromDigitsException => syntaxErrorOrIncomplete(ex.getMessage) + case ex: FromDigitsException => syntaxErrorOrIncomplete(ex.getMessage.toMessage) } Literal(Constant(value)) } @@ -1365,9 +1352,9 @@ object Parsers { in.nextToken() if in.indentWidth(in.offset) == in.currentRegion.indentWidth then report.errorOrMigrationWarning( - i"""This opening brace will start a new statement in Scala 3. - |It needs to be indented to the right to keep being treated as - |an argument to the previous expression.${rewriteNotice()}""", + em"""This opening brace will start a new statement in Scala 3. + |It needs to be indented to the right to keep being treated as + |an argument to the previous expression.${rewriteNotice()}""", in.sourcePos(), from = `3.0`) patch(source, Span(in.offset), " ") @@ -1378,7 +1365,7 @@ object Parsers { else in.nextToken() if in.token != INDENT && in.token != LBRACE then - syntaxErrorOrIncomplete(i"indented definitions expected, ${in} found") + syntaxErrorOrIncomplete(em"indented definitions expected, ${in} found") else newLineOptWhenFollowedBy(LBRACE) @@ -1419,7 +1406,7 @@ object Parsers { if in.token == END then val start = in.skipToken() if stats.isEmpty || !matchesAndSetEnd(stats.last) then - syntaxError("misaligned end marker", Span(start, in.lastCharOffset)) + syntaxError(em"misaligned end marker", Span(start, in.lastCharOffset)) else if overlapsPatch(source, Span(start, start)) then patch(source, Span(start, start), "") patch(source, Span(start, in.lastCharOffset), s"} // end $endName") @@ -1506,7 +1493,7 @@ object Parsers { TermLambdaTypeTree(params.asInstanceOf[List[ValDef]], resultType) else if imods.isOneOf(Given | Erased | Impure) then if imods.is(Given) && params.isEmpty then - syntaxError("context function types require at least one parameter", paramSpan) + syntaxError(em"context function types require at least one parameter", paramSpan) FunctionWithMods(params, resultType, imods) else if !ctx.settings.YkindProjector.isDefault then val (newParams :+ newResultType, tparams) = replaceKindProjectorPlaceholders(params :+ resultType): @unchecked @@ -1569,7 +1556,7 @@ object Parsers { if (isFunction(body)) PolyFunction(tparams, body) else { - syntaxError("Implementation restriction: polymorphic function types must have a value parameter", arrowOffset) + syntaxError(em"Implementation restriction: polymorphic function types must have a value parameter", arrowOffset) Ident(nme.ERROR.toTypeName) } } @@ -1723,7 +1710,7 @@ object Parsers { val hint = if inPattern then "Use lower cased variable name without the `$` instead" else "To use a given Type[T] in a quote just write T directly" - syntaxError(s"$msg\n\nHint: $hint", Span(start, in.lastOffset)) + syntaxError(em"$msg\n\nHint: $hint", Span(start, in.lastOffset)) Ident(nme.ERROR.toTypeName) else Splice(expr) @@ -1744,7 +1731,7 @@ object Parsers { Ident(tpnme.USCOREkw).withSpan(Span(start, in.lastOffset, start)) else if sourceVersion.isAtLeast(future) then - deprecationWarning(em"`_` is deprecated for wildcard arguments of types: use `?` instead".toMessage) + deprecationWarning(em"`_` is deprecated for wildcard arguments of types: use `?` instead") patch(source, Span(in.offset, in.offset + 1), "?") val start = in.skipToken() typeBounds().withSpan(Span(start, in.lastOffset, start)) @@ -1805,7 +1792,7 @@ object Parsers { if (!ctx.settings.YkindProjector.isDefault) { def fail(): Tree = { syntaxError( - "λ requires a single argument of the form X => ... or (X, Y) => ...", + em"λ requires a single argument of the form X => ... or (X, Y) => ...", Span(startOffset(t), in.lastOffset) ) AppliedTypeTree(applied, args) @@ -1900,10 +1887,10 @@ object Parsers { val tp = paramTypeOf(core) val tp1 = tp match case ImpureByNameTypeTree(tp1) => - syntaxError("explicit captureSet is superfluous for impure call-by-name type", start) + syntaxError(em"explicit captureSet is superfluous for impure call-by-name type", start) tp1 case CapturingTypeTree(_, tp1: ByNameTypeTree) => - syntaxError("only one captureSet is allowed here", start) + syntaxError(em"only one captureSet is allowed here", start) tp1 case _: ByNameTypeTree if startTpOffset > endCsOffset => report.warning( @@ -1918,6 +1905,13 @@ object Parsers { else core() + private def maybeInto(tp: () => Tree) = + if in.isIdent(nme.into) + && in.featureEnabled(Feature.into) + && canStartTypeTokens.contains(in.lookahead.token) + then atSpan(in.skipToken()) { Into(tp()) } + else tp() + /** FunArgType ::= Type * | `=>' Type * | [CaptureSet] `->' Type @@ -1930,10 +1924,10 @@ object Parsers { */ def paramType(): Tree = paramTypeOf(paramValueType) - /** ParamValueType ::= Type [`*'] + /** ParamValueType ::= [`into`] Type [`*'] */ def paramValueType(): Tree = { - val t = toplevelTyp() + val t = maybeInto(toplevelTyp) if (isIdent(nme.raw.STAR)) { in.nextToken() atSpan(startOffset(t)) { PostfixOp(t, Ident(tpnme.raw.STAR)) } @@ -1979,7 +1973,7 @@ object Parsers { } :: contextBounds(pname) else if in.token == VIEWBOUND then report.errorOrMigrationWarning( - "view bounds `<%' are no longer supported, use a context bound `:' instead", + em"view bounds `<%' are no longer supported, use a context bound `:' instead", in.sourcePos(), from = `3.0`) atSpan(in.skipToken()) { Function(Ident(pname) :: Nil, toplevelTyp()) @@ -2095,7 +2089,7 @@ object Parsers { if (isFunction(body)) PolyFunction(tparams, body) else { - syntaxError("Implementation restriction: polymorphic function literals must have a value parameter", arrowOffset) + syntaxError(em"Implementation restriction: polymorphic function literals must have a value parameter", arrowOffset) errorTermTree(arrowOffset) } } @@ -2130,8 +2124,8 @@ object Parsers { } case DO => report.errorOrMigrationWarning( - i"""`do while ` is no longer supported, - |use `while ; do ()` instead.${rewriteNotice()}""", + em"""`do while ` is no longer supported, + |use `while ; do ()` instead.${rewriteNotice()}""", in.sourcePos(), from = `3.0`) val start = in.skipToken() atSpan(start) { @@ -2319,7 +2313,7 @@ object Parsers { val t = if ((in.token == COLONop || in.token == COLONfollow) && location == Location.InBlock) { report.errorOrMigrationWarning( - s"This syntax is no longer supported; parameter needs to be enclosed in (...)${rewriteNotice(`future-migration`)}", + em"This syntax is no longer supported; parameter needs to be enclosed in (...)${rewriteNotice(`future-migration`)}", source.atSpan(Span(start, in.lastOffset)), from = future) in.nextToken() @@ -2356,7 +2350,7 @@ object Parsers { atSpan(start, in.offset) { if in.token == CTXARROW then if params.isEmpty then - syntaxError("context function literals require at least one formal parameter", Span(start, in.lastOffset)) + syntaxError(em"context function literals require at least one formal parameter", Span(start, in.lastOffset)) in.nextToken() else accept(ARROW) @@ -2779,10 +2773,10 @@ object Parsers { CaseDef(pat, grd, atSpan(accept(ARROW)) { if exprOnly then if in.indentSyntax && in.isAfterLineEnd && in.token != INDENT then - warning(i"""Misleading indentation: this expression forms part of the preceding catch case. - |If this is intended, it should be indented for clarity. - |Otherwise, if the handler is intended to be empty, use a multi-line catch with - |an indented case.""".toMessage) + warning(em"""Misleading indentation: this expression forms part of the preceding catch case. + |If this is intended, it should be indented for clarity. + |Otherwise, if the handler is intended to be empty, use a multi-line catch with + |an indented case.""") expr() else block() }) @@ -3007,7 +3001,7 @@ object Parsers { if in.token == THIS then if sourceVersion.isAtLeast(future) then deprecationWarning( - "The [this] qualifier will be deprecated in the future; it should be dropped.".toMessage) + em"The [this] qualifier will be deprecated in the future; it should be dropped.") in.nextToken() mods | Local else mods.withPrivateWithin(ident().toTypeName) @@ -3098,7 +3092,7 @@ object Parsers { def variance(vflag: FlagSet): FlagSet = if ownerKind == ParamOwner.Def || ownerKind == ParamOwner.TypeParam then - syntaxError(i"no `+/-` variance annotation allowed here") + syntaxError(em"no `+/-` variance annotation allowed here") in.nextToken() EmptyFlags else @@ -3189,7 +3183,7 @@ object Parsers { addMod(mods, mod) else if (!(mods.flags &~ (ParamAccessor | Inline | impliedMods.flags)).isEmpty) - syntaxError("`val` or `var` expected") + syntaxError(em"`val` or `var` expected") if (firstClause && ofCaseClass) mods else mods | PrivateLocal } @@ -3235,7 +3229,7 @@ object Parsers { else paramMods() if givenOnly && !impliedMods.is(Given) then - syntaxError("`using` expected") + syntaxError(em"`using` expected") val isParams = !impliedMods.is(Given) || startParamTokens.contains(in.token) @@ -3314,19 +3308,19 @@ object Parsers { in.languageImportContext = in.languageImportContext.importContext(imp, NoSymbol) for case ImportSelector(id @ Ident(imported), EmptyTree, _) <- selectors do if Feature.handleGlobalLanguageImport(prefix, imported) && !outermost then - syntaxError(i"this language import is only allowed at the toplevel", id.span) + syntaxError(em"this language import is only allowed at the toplevel", id.span) if allSourceVersionNames.contains(imported) && prefix.isEmpty then if !outermost then - syntaxError(i"source version import is only allowed at the toplevel", id.span) + syntaxError(em"source version import is only allowed at the toplevel", id.span) else if ctx.compilationUnit.sourceVersion.isDefined then - syntaxError(i"duplicate source version import", id.span) + syntaxError(em"duplicate source version import", id.span) else if illegalSourceVersionNames.contains(imported) then val candidate = val nonMigration = imported.toString.replace("-migration", "") validSourceVersionNames.find(_.show == nonMigration) - val baseMsg = i"`$imported` is not a valid source version" + val baseMsg = em"`$imported` is not a valid source version" val msg = candidate match - case Some(member) => i"$baseMsg, did you mean language.`$member`?" + case Some(member) => baseMsg.append(i", did you mean language.`$member`?") case _ => baseMsg syntaxError(msg, id.span) else @@ -3389,7 +3383,7 @@ object Parsers { case _ => if isIdent(nme.raw.STAR) then wildcardSelector() else - if !idOK then syntaxError(i"named imports cannot follow wildcard imports") + if !idOK then syntaxError(em"named imports cannot follow wildcard imports") namedSelector(termIdent()) } @@ -3489,7 +3483,7 @@ object Parsers { if sourceVersion.isAtLeast(future) then deprecationWarning( em"""`= _` has been deprecated; use `= uninitialized` instead. - |`uninitialized` can be imported with `scala.compiletime.uninitialized`.""".toMessage, + |`uninitialized` can be imported with `scala.compiletime.uninitialized`.""", rhsOffset) placeholderParams = placeholderParams.tail atSpan(rhs0.span) { Ident(nme.WILDCARD) } @@ -3530,7 +3524,7 @@ object Parsers { else ": Unit " // trailing space ensures that `def f()def g()` works. if migrateTo3 then report.errorOrMigrationWarning( - s"Procedure syntax no longer supported; `$toInsert` should be inserted here", + em"Procedure syntax no longer supported; `$toInsert` should be inserted here", in.sourcePos(), from = `3.0`) patch(source, Span(in.lastOffset), toInsert) true @@ -3542,7 +3536,7 @@ object Parsers { val vparamss = paramClauses(numLeadParams = numLeadParams) if (vparamss.isEmpty || vparamss.head.take(1).exists(_.mods.isOneOf(GivenOrImplicit))) in.token match { - case LBRACKET => syntaxError("no type parameters allowed here") + case LBRACKET => syntaxError(em"no type parameters allowed here") case EOF => incompleteInputError(AuxConstructorNeedsNonImplicitParameter()) case _ => syntaxError(AuxConstructorNeedsNonImplicitParameter(), nameStart) } @@ -3635,13 +3629,13 @@ object Parsers { case TypeBoundsTree(EmptyTree, upper, _) => rhs = MatchTypeTree(upper, mtt.selector, mtt.cases) case _ => - syntaxError(i"cannot combine lower bound and match type alias", eqOffset) + syntaxError(em"cannot combine lower bound and match type alias", eqOffset) } case _ => if mods.is(Opaque) then rhs = TypeBoundsTree(bounds.lo, bounds.hi, rhs) else - syntaxError(i"cannot combine bound and alias", eqOffset) + syntaxError(em"cannot combine bound and alias", eqOffset) } makeTypeDef(rhs) } @@ -3722,7 +3716,7 @@ object Parsers { private def checkAccessOnly(mods: Modifiers, where: String): Modifiers = val mods1 = mods & (AccessFlags | Enum) if mods1 ne mods then - syntaxError(s"Only access modifiers are allowed on enum $where") + syntaxError(em"Only access modifiers are allowed on enum $where") mods1 /** EnumDef ::= id ClassConstr InheritClauses EnumBody @@ -3778,17 +3772,17 @@ object Parsers { vparamss: List[List[Tree]], stat: Tree): Unit = stat match { case stat: DefDef => if stat.mods.is(ExtensionMethod) && vparamss.nonEmpty then - syntaxError(i"no extension method allowed here since leading parameter was already given", stat.span) + syntaxError(em"no extension method allowed here since leading parameter was already given", stat.span) else if !stat.mods.is(ExtensionMethod) && vparamss.isEmpty then - syntaxError(i"an extension method is required here", stat.span) + syntaxError(em"an extension method is required here", stat.span) else if tparams.nonEmpty && stat.leadingTypeParams.nonEmpty then - syntaxError(i"extension method cannot have type parameters since some were already given previously", + syntaxError(em"extension method cannot have type parameters since some were already given previously", stat.leadingTypeParams.head.span) else if stat.rhs.isEmpty then - syntaxError(i"extension method cannot be abstract", stat.span) + syntaxError(em"extension method cannot be abstract", stat.span) case EmptyTree => case stat => - syntaxError(i"extension clause can only define methods", stat.span) + syntaxError(em"extension clause can only define methods", stat.span) } /** GivenDef ::= [GivenSig] (AnnotType [‘=’ Expr] | StructuralInstance) @@ -3854,7 +3848,7 @@ object Parsers { do () leadParamss ++= paramClauses(givenOnly = true, numLeadParams = nparams) if in.isColon then - syntaxError("no `:` expected here") + syntaxError(em"no `:` expected here") in.nextToken() val methods: List[Tree] = if in.token == EXPORT then @@ -3865,7 +3859,7 @@ object Parsers { in.observeIndented() newLineOptWhenFollowedBy(LBRACE) if in.isNestedStart then inDefScopeBraces(extMethods(nparams)) - else { syntaxErrorOrIncomplete("Extension without extension methods") ; Nil } + else { syntaxErrorOrIncomplete(em"Extension without extension methods") ; Nil } val result = atSpan(start)(ExtMethods(joinParams(tparams, leadParamss.toList), methods)) val comment = in.getDocComment(start) if comment.isDefined then @@ -3898,7 +3892,7 @@ object Parsers { meths += defDefOrDcl(start, mods, numLeadParams) in.token != EOF && statSepOrEnd(meths, what = "extension method") do () - if meths.isEmpty then syntaxErrorOrIncomplete("`def` expected") + if meths.isEmpty then syntaxErrorOrIncomplete(em"`def` expected") meths.toList } @@ -3944,7 +3938,7 @@ object Parsers { in.nextToken() if (in.token == LBRACE || in.token == COLONeol) { report.errorOrMigrationWarning( - "`extends` must be followed by at least one parent", + em"`extends` must be followed by at least one parent", in.sourcePos(), from = `3.0`) Nil } @@ -4086,7 +4080,7 @@ object Parsers { in.token = SELFARROW // suppresses INDENT insertion after `=>` in.nextToken() else - syntaxError("`=>` expected after self type") + syntaxError(em"`=>` expected after self type") makeSelfDef(selfName, selfTpt) } else EmptyValDef @@ -4133,24 +4127,26 @@ object Parsers { def refineStatSeq(): List[Tree] = { val stats = new ListBuffer[Tree] def checkLegal(tree: Tree): List[Tree] = - val problem = tree match + def ok = tree :: Nil + def fail(msg: Message) = + syntaxError(msg, tree.span) + Nil + tree match case tree: ValDef if tree.mods.is(Mutable) => - i"""refinement cannot be a mutable var. - |You can use an explicit getter ${tree.name} and setter ${tree.name}_= instead""" + fail(em"""refinement cannot be a mutable var. + |You can use an explicit getter ${tree.name} and setter ${tree.name}_= instead""") case tree: MemberDef if !(tree.mods.flags & ModifierFlags).isEmpty => - i"refinement cannot be ${(tree.mods.flags & ModifierFlags).flagStrings().mkString("`", "`, `", "`")}" + fail(em"refinement cannot be ${(tree.mods.flags & ModifierFlags).flagStrings().mkString("`", "`, `", "`")}") case tree: DefDef if tree.termParamss.nestedExists(!_.rhs.isEmpty) => - i"refinement cannot have default arguments" + fail(em"refinement cannot have default arguments") case tree: ValOrDefDef => - if tree.rhs.isEmpty then "" - else "refinement cannot have a right-hand side" + if tree.rhs.isEmpty then ok + else fail(em"refinement cannot have a right-hand side") case tree: TypeDef => - if !tree.isClassDef then "" - else "refinement cannot be a class or trait" + if !tree.isClassDef then ok + else fail(em"refinement cannot be a class or trait") case _ => - "this kind of definition cannot be a refinement" - if problem.isEmpty then tree :: Nil - else { syntaxError(problem, tree.span); Nil } + fail(em"this kind of definition cannot be a refinement") while val dclFound = isDclIntro diff --git a/tests/pos-with-compiler-cc/dotc/parsing/Scanners.scala b/tests/pos-with-compiler-cc/dotc/parsing/Scanners.scala index a1165c44c09e..7e16447ae4ce 100644 --- a/tests/pos-with-compiler-cc/dotc/parsing/Scanners.scala +++ b/tests/pos-with-compiler-cc/dotc/parsing/Scanners.scala @@ -19,7 +19,7 @@ import rewrites.Rewrites.patch import config.Feature import config.Feature.{migrateTo3, fewerBracesEnabled} import config.SourceVersion.`3.0` -import reporting.{NoProfile, Profile} +import reporting.{NoProfile, Profile, Message} object Scanners { @@ -100,19 +100,23 @@ object Scanners { */ var errOffset: Offset = NoOffset + /** Implements CharArrayReader's error method */ + protected def error(msg: String, off: Offset): Unit = + error(msg.toMessage, off) + /** Generate an error at the given offset */ - def error(msg: String, off: Offset = offset): Unit = { + def error(msg: Message, off: Offset = offset): Unit = { errorButContinue(msg, off) token = ERROR errOffset = off } - def errorButContinue(msg: String, off: Offset = offset): Unit = + def errorButContinue(msg: Message, off: Offset = offset): Unit = report.error(msg, sourcePos(off)) /** signal an error where the input ended in the middle of a token */ - def incompleteInputError(msg: String): Unit = { - report.incompleteInputError(msg.toMessage, sourcePos()) + def incompleteInputError(msg: Message): Unit = { + report.incompleteInputError(msg, sourcePos()) token = EOF errOffset = offset } @@ -159,7 +163,7 @@ object Scanners { // disallow trailing numeric separator char, but continue lexing def checkNoTrailingSeparator(): Unit = if (!litBuf.isEmpty && isNumberSeparator(litBuf.last)) - errorButContinue("trailing separator is not allowed", offset + litBuf.length - 1) + errorButContinue(em"trailing separator is not allowed", offset + litBuf.length - 1) } class Scanner(source: SourceFile, override val startFrom: Offset = 0, profile: Profile = NoProfile, allowIndent: Boolean = true)(using Context) extends ScannerCommon(source) { @@ -192,7 +196,7 @@ object Scanners { val rewriteTargets = List(s.newSyntax, s.oldSyntax, s.indent, s.noindent) val enabled = rewriteTargets.filter(_.value) if (enabled.length > 1) - error(s"illegal combination of -rewrite targets: ${enabled(0).name} and ${enabled(1).name}") + error(em"illegal combination of -rewrite targets: ${enabled(0).name} and ${enabled(1).name}") } private var myLanguageImportContext: Context = ctx @@ -245,7 +249,7 @@ object Scanners { if scala3keywords.contains(keyword) && migrateTo3 then val what = tokenString(keyword) report.errorOrMigrationWarning( - i"$what is now a keyword, write `$what` instead of $what to keep it as an identifier", + em"$what is now a keyword, write `$what` instead of $what to keep it as an identifier", sourcePos(), from = `3.0`) patch(source, Span(offset), "`") @@ -603,9 +607,9 @@ object Scanners { insert(OUTDENT, offset) handleNewIndentWidth(r.enclosing, ir => errorButContinue( - i"""The start of this line does not match any of the previous indentation widths. - |Indentation width of current line : $nextWidth - |This falls between previous widths: ${ir.width} and $lastWidth""")) + em"""The start of this line does not match any of the previous indentation widths. + |Indentation width of current line : $nextWidth + |This falls between previous widths: ${ir.width} and $lastWidth""")) case r => if skipping then if r.enclosing.isClosedByUndentAt(nextWidth) then @@ -628,9 +632,9 @@ object Scanners { profile.recordNewLine() end handleNewLine - def spaceTabMismatchMsg(lastWidth: IndentWidth, nextWidth: IndentWidth) = - i"""Incompatible combinations of tabs and spaces in indentation prefixes. - |Previous indent : $lastWidth + def spaceTabMismatchMsg(lastWidth: IndentWidth, nextWidth: IndentWidth): Message = + em"""Incompatible combinations of tabs and spaces in indentation prefixes. + |Previous indent : $lastWidth |Latest indent : $nextWidth""" def observeColonEOL(inTemplate: Boolean): Unit = @@ -783,12 +787,12 @@ object Scanners { putChar(low) res = true else - error(s"illegal character '${toUnicode(high)}${toUnicode(low)}'") + error(em"illegal character '${toUnicode(high)}${toUnicode(low)}'") else if !strict then putChar(high) res = true else - error(s"illegal character '${toUnicode(high)}' missing low surrogate") + error(em"illegal character '${toUnicode(high)}' missing low surrogate") res } private def atSupplementary(ch: Char, f: Int => Boolean): Boolean = @@ -865,7 +869,7 @@ object Scanners { case _ => base = 10 ; putChar('0') } if (base != 10 && !isNumberSeparator(ch) && digit2int(ch, base) < 0) - error("invalid literal number") + error(em"invalid literal number") } fetchLeadingZero() getNumber() @@ -931,13 +935,13 @@ object Scanners { val isEmptyCharLit = (ch == '\'') getLitChar() if ch == '\'' then - if isEmptyCharLit then error("empty character literal (use '\\'' for single quote)") - else if litBuf.length != 1 then error("illegal codepoint in Char constant: " + litBuf.toString.map(toUnicode).mkString("'", "", "'")) + if isEmptyCharLit then error(em"empty character literal (use '\\'' for single quote)") + else if litBuf.length != 1 then error(em"illegal codepoint in Char constant: ${litBuf.toString.map(toUnicode).mkString("'", "", "'")}") else finishCharLit() - else if isEmptyCharLit then error("empty character literal") - else error("unclosed character literal") + else if isEmptyCharLit then error(em"empty character literal") + else error(em"unclosed character literal") case _ => - error("unclosed character literal") + error(em"unclosed character literal") } } fetchSingleQuote() @@ -968,18 +972,18 @@ object Scanners { case SU => if (isAtEnd) token = EOF else { - error("illegal character") + error(em"illegal character") nextChar() } case _ => def fetchOther() = if (ch == '\u21D2') { nextChar(); token = ARROW - report.deprecationWarning("The unicode arrow `⇒` is deprecated, use `=>` instead. If you still wish to display it as one character, consider using a font with programming ligatures such as Fira Code.", sourcePos(offset)) + report.deprecationWarning(em"The unicode arrow `⇒` is deprecated, use `=>` instead. If you still wish to display it as one character, consider using a font with programming ligatures such as Fira Code.", sourcePos(offset)) } else if (ch == '\u2190') { nextChar(); token = LARROW - report.deprecationWarning("The unicode arrow `←` is deprecated, use `<-` instead. If you still wish to display it as one character, consider using a font with programming ligatures such as Fira Code.", sourcePos(offset)) + report.deprecationWarning(em"The unicode arrow `←` is deprecated, use `<-` instead. If you still wish to display it as one character, consider using a font with programming ligatures such as Fira Code.", sourcePos(offset)) } else if (Character.isUnicodeIdentifierStart(ch)) { putChar(ch) @@ -994,7 +998,7 @@ object Scanners { else if isSupplementary(ch, isUnicodeIdentifierStart) then getIdentRest() else { - error(s"illegal character '${toUnicode(ch)}'") + error(em"illegal character '${toUnicode(ch)}'") nextChar() } fetchOther() @@ -1024,7 +1028,7 @@ object Scanners { if (ch == '/') nextChar() else skipComment() } - else if (ch == SU) incompleteInputError("unclosed comment") + else if (ch == SU) incompleteInputError(em"unclosed comment") else { nextChar(); skipComment() } def nestedComment() = { nextChar(); skipComment() } val start = lastCharOffset @@ -1104,11 +1108,11 @@ object Scanners { nextChar() finishNamedToken(BACKQUOTED_IDENT, target = this) if (name.length == 0) - error("empty quoted identifier") + error(em"empty quoted identifier") else if (name == nme.WILDCARD) - error("wildcard invalid as backquoted identifier") + error(em"wildcard invalid as backquoted identifier") } - else error("unclosed quoted identifier") + else error(em"unclosed quoted identifier") } private def getIdentRest(): Unit = (ch: @switch) match { @@ -1202,7 +1206,7 @@ object Scanners { nextChar() token = STRINGLIT } - else error("unclosed string literal") + else error(em"unclosed string literal") } private def getRawStringLit(): Unit = @@ -1216,7 +1220,7 @@ object Scanners { getRawStringLit() } else if (ch == SU) - incompleteInputError("unclosed multi-line string literal") + incompleteInputError(em"unclosed multi-line string literal") else { putChar(ch) nextRawChar() @@ -1286,7 +1290,7 @@ object Scanners { else if atSupplementary(ch, isUnicodeIdentifierStart) then getInterpolatedIdentRest(hasSupplement = true) else - error("invalid string interpolation: `$$`, `$\"`, `$`ident or `$`BlockExpr expected", off = charOffset - 2) + error("invalid string interpolation: `$$`, `$\"`, `$`ident or `$`BlockExpr expected".toMessage, off = charOffset - 2) putChar('$') getStringPart(multiLine) } @@ -1294,9 +1298,9 @@ object Scanners { val isUnclosedLiteral = !isUnicodeEscape && (ch == SU || (!multiLine && (ch == CR || ch == LF))) if (isUnclosedLiteral) if (multiLine) - incompleteInputError("unclosed multi-line string literal") + incompleteInputError(em"unclosed multi-line string literal") else - error("unclosed string literal") + error(em"unclosed string literal") else { putChar(ch) nextRawChar() @@ -1448,7 +1452,7 @@ object Scanners { } def checkNoLetter(): Unit = if (isIdentifierPart(ch) && ch >= ' ') - error("Invalid literal number") + error(em"Invalid literal number") /** Read a number into strVal and set base */ @@ -1531,7 +1535,7 @@ object Scanners { def resume(lastTokenData: TokenData): Unit = { this.copyFrom(lastTokenData) if (next.token != EMPTY && !ctx.reporter.hasErrors) - error("unexpected end of input: possible missing '}' in XML block") + error(em"unexpected end of input: possible missing '}' in XML block") nextToken() } diff --git a/tests/pos-with-compiler-cc/dotc/parsing/Tokens.scala b/tests/pos-with-compiler-cc/dotc/parsing/Tokens.scala index 7d27b3ca82b9..dba0ad3fa2ee 100644 --- a/tests/pos-with-compiler-cc/dotc/parsing/Tokens.scala +++ b/tests/pos-with-compiler-cc/dotc/parsing/Tokens.scala @@ -231,6 +231,8 @@ object Tokens extends TokensCommon { final val canStartInfixTypeTokens: TokenSet = literalTokens | identifierTokens | BitSet( THIS, SUPER, USCORE, LPAREN, LBRACE, AT) + final val canStartTypeTokens: TokenSet = canStartInfixTypeTokens | BitSet(LBRACE) + final val templateIntroTokens: TokenSet = BitSet(CLASS, TRAIT, OBJECT, ENUM, CASECLASS, CASEOBJECT) final val dclIntroTokens: TokenSet = BitSet(DEF, VAL, VAR, TYPE, GIVEN) @@ -287,7 +289,7 @@ object Tokens extends TokensCommon { final val closingParens = BitSet(RPAREN, RBRACKET, RBRACE) - final val softModifierNames = Set(nme.inline, nme.opaque, nme.open, nme.transparent, nme.infix) + final val softModifierNames = Set(nme.inline, nme.into, nme.opaque, nme.open, nme.transparent, nme.infix) def showTokenDetailed(token: Int): String = debugString(token) diff --git a/tests/pos-with-compiler-cc/dotc/parsing/xml/MarkupParsers.scala b/tests/pos-with-compiler-cc/dotc/parsing/xml/MarkupParsers.scala index 3d9f5fb7ad6d..77c5a1bf376b 100644 --- a/tests/pos-with-compiler-cc/dotc/parsing/xml/MarkupParsers.scala +++ b/tests/pos-with-compiler-cc/dotc/parsing/xml/MarkupParsers.scala @@ -6,6 +6,7 @@ package xml import scala.language.unsafeNulls import scala.collection.mutable +import core.Contexts.Context import mutable.{ Buffer, ArrayBuffer, ListBuffer } import scala.util.control.ControlThrowable import util.Chars.SU @@ -13,7 +14,7 @@ import Parsers._ import util.Spans._ import core._ import Constants._ -import Decorators.toMessage +import Decorators.{em, toMessage} import util.SourceFile import Utility._ @@ -50,7 +51,7 @@ object MarkupParsers { override def getMessage: String = "input ended while parsing XML" } - class MarkupParser(parser: Parser, final val preserveWS: Boolean)(implicit src: SourceFile) extends MarkupParserCommon { + class MarkupParser(parser: Parser, final val preserveWS: Boolean)(using Context) extends MarkupParserCommon { import Tokens.{ LBRACE, RBRACE } @@ -330,9 +331,9 @@ object MarkupParsers { case c @ TruncatedXMLControl => ifTruncated(c.getMessage) case c @ (MissingEndTagControl | ConfusedAboutBracesControl) => - parser.syntaxError(c.getMessage + debugLastElem + ">", debugLastPos) + parser.syntaxError(em"${c.getMessage}$debugLastElem>", debugLastPos) case _: ArrayIndexOutOfBoundsException => - parser.syntaxError("missing end tag in XML literal for <%s>" format debugLastElem, debugLastPos) + parser.syntaxError(em"missing end tag in XML literal for <$debugLastElem>", debugLastPos) } finally parser.in.resume(saved) @@ -396,7 +397,7 @@ object MarkupParsers { tree } }, - msg => parser.syntaxError(msg, curOffset) + msg => parser.syntaxError(msg.toMessage, curOffset) ) def escapeToScala[A](op: => A, kind: String): A = { @@ -422,7 +423,7 @@ object MarkupParsers { */ def xScalaPatterns: List[Tree] = escapeToScala(parser.patterns(), "pattern") - def reportSyntaxError(offset: Int, str: String): Unit = parser.syntaxError(str, offset) + def reportSyntaxError(offset: Int, str: String): Unit = parser.syntaxError(str.toMessage, offset) def reportSyntaxError(str: String): Unit = { reportSyntaxError(curOffset, "in XML literal: " + str) nextch() diff --git a/tests/pos-with-compiler-cc/dotc/plugins/Plugins.scala b/tests/pos-with-compiler-cc/dotc/plugins/Plugins.scala index 3093a1c0460f..976b783c40f0 100644 --- a/tests/pos-with-compiler-cc/dotc/plugins/Plugins.scala +++ b/tests/pos-with-compiler-cc/dotc/plugins/Plugins.scala @@ -5,6 +5,7 @@ import scala.language.unsafeNulls import core._ import Contexts._ +import Decorators.em import config.{ PathResolver, Feature } import dotty.tools.io._ import Phases._ @@ -83,14 +84,14 @@ trait Plugins { // Verify required plugins are present. for (req <- ctx.settings.require.value ; if !(plugs exists (_.name == req))) - report.error("Missing required plugin: " + req) + report.error(em"Missing required plugin: $req") // Verify no non-existent plugin given with -P for { opt <- ctx.settings.pluginOptions.value if !(plugs exists (opt startsWith _.name + ":")) } - report.error("bad option: -P:" + opt) + report.error(em"bad option: -P:$opt") plugs } diff --git a/tests/pos-with-compiler-cc/dotc/printing/Formatting.scala b/tests/pos-with-compiler-cc/dotc/printing/Formatting.scala index f85845517d8c..f4bbd74842c8 100644 --- a/tests/pos-with-compiler-cc/dotc/printing/Formatting.scala +++ b/tests/pos-with-compiler-cc/dotc/printing/Formatting.scala @@ -137,236 +137,6 @@ object Formatting { } } - /** The `em` string interpolator works like the `i` string interpolator, but marks nonsensical errors - * using `...` tags. - * Note: Instead of these tags, it would be nicer to return a data structure containing the message string - * and a boolean indicating whether the message is sensical, but then we cannot use string operations - * like concatenation, stripMargin etc on the values returned by em"...", and in the current error - * message composition methods, this is crucial. - */ - def forErrorMessages(op: Context ?=> String)(using Context): String = op(using errorMessageCtx) - - private class ErrorMessagePrinter(_ctx: Context) extends RefinedPrinter(_ctx): - override def toText(tp: Type): Text = wrapNonSensical(tp, super.toText(tp)) - override def toText(sym: Symbol): Text = wrapNonSensical(sym, super.toText(sym)) - - private def wrapNonSensical(arg: Any, text: Text)(using Context): Text = { - import Message._ - def isSensical(arg: Any): Boolean = arg match { - case tpe: Type => - tpe.exists && !tpe.isErroneous - case sym: Symbol if sym.isCompleted => - sym.info match { - case _: ErrorType | TypeAlias(_: ErrorType) | NoType => false - case _ => true - } - case _ => true - } - - if (isSensical(arg)) text - else nonSensicalStartTag ~ text ~ nonSensicalEndTag - } - - private type Recorded = Symbol | ParamRef | SkolemType - - private case class SeenKey(str: String, isType: Boolean) - private class Seen extends mutable.HashMap[SeenKey, List[Recorded]] { - - override def default(key: SeenKey) = Nil - - def record(str: String, isType: Boolean, entry: Recorded)(using Context): String = { - - /** If `e1` is an alias of another class of the same name, return the other - * class symbol instead. This normalization avoids recording e.g. scala.List - * and scala.collection.immutable.List as two different types - */ - def followAlias(e1: Recorded): Recorded = e1 match { - case e1: Symbol if e1.isAliasType => - val underlying = e1.typeRef.underlyingClassRef(refinementOK = false).typeSymbol - if (underlying.name == e1.name) underlying else e1 - case _ => e1 - } - val key = SeenKey(str, isType) - val existing = apply(key) - lazy val dealiased = followAlias(entry) - - // alts: The alternatives in `existing` that are equal, or follow (an alias of) `entry` - var alts = existing.dropWhile(alt => dealiased ne followAlias(alt)) - if (alts.isEmpty) { - alts = entry :: existing - update(key, alts) - } - val suffix = alts.length match { - case 1 => "" - case n => n.toString.toCharArray.map { - case '0' => '⁰' - case '1' => '¹' - case '2' => '²' - case '3' => '³' - case '4' => '⁴' - case '5' => '⁵' - case '6' => '⁶' - case '7' => '⁷' - case '8' => '⁸' - case '9' => '⁹' - }.mkString - } - str + suffix - } - } - - private class ExplainingPrinter(seen: Seen)(_ctx: Context) extends ErrorMessagePrinter(_ctx) { - - /** True if printer should a source module instead of its module class */ - private def useSourceModule(sym: Symbol): Boolean = - sym.is(ModuleClass, butNot = Package) && sym.sourceModule.exists && !_ctx.settings.YdebugNames.value - - override def simpleNameString(sym: Symbol): String = - if (useSourceModule(sym)) simpleNameString(sym.sourceModule) - else seen.record(super.simpleNameString(sym), sym.isType, sym) - - override def ParamRefNameString(param: ParamRef): String = - seen.record(super.ParamRefNameString(param), param.isInstanceOf[TypeParamRef], param) - - override def toTextRef(tp: SingletonType): Text = tp match { - case tp: SkolemType => seen.record(tp.repr.toString, isType = true, tp) - case _ => super.toTextRef(tp) - } - - override def toText(tp: Type): Text = tp match { - case tp: TypeRef if useSourceModule(tp.symbol) => Str("object ") ~ super.toText(tp) - case _ => super.toText(tp) - } - } - - /** Create explanation for single `Recorded` type or symbol */ - def explanation(entry: AnyRef)(using Context): String = { - def boundStr(bound: Type, default: ClassSymbol, cmp: String) = - if (bound.isRef(default)) "" else i"$cmp $bound" - - def boundsStr(bounds: TypeBounds): String = { - val lo = boundStr(bounds.lo, defn.NothingClass, ">:") - val hi = boundStr(bounds.hi, defn.AnyClass, "<:") - if (lo.isEmpty) hi - else if (hi.isEmpty) lo - else s"$lo and $hi" - } - - def addendum(cat: String, info: Type): String = info match { - case bounds @ TypeBounds(lo, hi) if bounds ne TypeBounds.empty => - if (lo eq hi) i" which is an alias of $lo" - else i" with $cat ${boundsStr(bounds)}" - case _ => - "" - } - - entry match { - case param: TypeParamRef => - s"is a type variable${addendum("constraint", TypeComparer.bounds(param))}" - case param: TermParamRef => - s"is a reference to a value parameter" - case sym: Symbol => - val info = - if (ctx.gadt.contains(sym)) - sym.info & ctx.gadt.fullBounds(sym) - else - sym.info - s"is a ${ctx.printer.kindString(sym)}${sym.showExtendedLocation}${addendum("bounds", info)}" - case tp: SkolemType => - s"is an unknown value of type ${tp.widen.show}" - } - } - - /** Turns a `Seen` into a `String` to produce an explanation for types on the - * form `where: T is...` - * - * @return string disambiguating types - */ - private def explanations(seen: Seen)(using Context): String = { - def needsExplanation(entry: Recorded) = entry match { - case param: TypeParamRef => ctx.typerState.constraint.contains(param) - case param: ParamRef => false - case skolem: SkolemType => true - case sym: Symbol => - ctx.gadt.contains(sym) && ctx.gadt.fullBounds(sym) != TypeBounds.empty - } - - val toExplain: List[(String, Recorded)] = seen.toList.flatMap { kvs => - val res: List[(String, Recorded)] = kvs match { - case (key, entry :: Nil) => - if (needsExplanation(entry)) (key.str, entry) :: Nil else Nil - case (key, entries) => - for (alt <- entries) yield { - val tickedString = seen.record(key.str, key.isType, alt) - (tickedString, alt) - } - } - res // help the inferrencer out - }.sortBy(_._1) - - def columnar(parts: List[(String, String)]): List[String] = { - lazy val maxLen = parts.map(_._1.length).max - parts.map { - case (leader, trailer) => - val variable = hl(leader) - s"""$variable${" " * (maxLen - leader.length)} $trailer""" - } - } - - val explainParts = toExplain.map { case (str, entry) => (str, explanation(entry)) } - val explainLines = columnar(explainParts) - if (explainLines.isEmpty) "" else i"where: $explainLines%\n %\n" - } - - private def errorMessageCtx(using Context): Context = - val ctx1 = ctx.property(MessageLimiter) match - case Some(_: ErrorMessageLimiter) => ctx - case _ => ctx.fresh.setProperty(MessageLimiter, ErrorMessageLimiter()) - ctx1.printer match - case _: ErrorMessagePrinter => ctx1 - case _ => ctx1.fresh.setPrinterFn(ctx => ErrorMessagePrinter(ctx)) - - /** Context with correct printer set for explanations */ - private def explainCtx(seen: Seen)(using Context): Context = - val ectx = errorMessageCtx - ectx.printer match - case dp: ExplainingPrinter => - ectx // re-use outer printer and defer explanation to it - case _ => - ectx.fresh.setPrinterFn(ctx => new ExplainingPrinter(seen)(ctx)) - - /** Entrypoint for explanation string interpolator: - * - * ``` - * ex"disambiguate $tpe1 and $tpe2" - * ``` - */ - def explained(op: Context ?=> String)(using Context): String = { - val seen = new Seen - val msg = op(using explainCtx(seen)) - val addendum = explanations(seen) - if (addendum.isEmpty) msg else msg ++ "\n\n" ++ addendum - } - - /** When getting a type mismatch it is useful to disambiguate placeholders like: - * - * ``` - * found: List[Int] - * required: List[T] - * where: T is a type in the initializer of value s which is an alias of - * String - * ``` - * - * @return the `where` section as well as the printing context for the - * placeholders - `("T is a...", printCtx)` - */ - def disambiguateTypes(args: Type*)(using Context): (String, Context) = { - val seen = new Seen - val printCtx = explainCtx(seen) - args.foreach(_.show(using printCtx)) // showing each member will put it into `seen` - (explanations(seen), printCtx) - } - /** This method will produce a colored type diff from the given arguments. * The idea is to do this for known cases that are useful and then fall back * on regular syntax highlighting for the cases which are unhandled. @@ -378,16 +148,13 @@ object Formatting { * @return the (found, expected, changePercentage) with coloring to * highlight the difference */ - def typeDiff(found: Type, expected: Type)(using Context): (String, String) = { - val fnd = wrapNonSensical(found, found.toText(ctx.printer)).show - val exp = wrapNonSensical(expected, expected.toText(ctx.printer)).show - - DiffUtil.mkColoredTypeDiff(fnd, exp) match { - case _ if ctx.settings.color.value == "never" => (fnd, exp) - case (fnd, exp, change) if change < 0.5 => (fnd, exp) + def typeDiff(found: Type, expected: Type)(using Context): (String, String) = + val fnd = found.show + val exp = expected.show + DiffUtil.mkColoredTypeDiff(fnd, exp) match + case (fnd1, exp1, change) + if change < 0.5 && ctx.settings.color.value != "never" => (fnd1, exp1) case _ => (fnd, exp) - } - } /** Explicit syntax highlighting */ def hl(s: String)(using Context): String = diff --git a/tests/pos-with-compiler-cc/dotc/printing/PlainPrinter.scala b/tests/pos-with-compiler-cc/dotc/printing/PlainPrinter.scala index 1a65b48ded41..0da1993310c6 100644 --- a/tests/pos-with-compiler-cc/dotc/printing/PlainPrinter.scala +++ b/tests/pos-with-compiler-cc/dotc/printing/PlainPrinter.scala @@ -224,7 +224,7 @@ class PlainPrinter(_ctx: Context) extends Printer { case tp: PreviousErrorType if ctx.settings.XprintTypes.value => "" // do not print previously reported error message because they may try to print this error type again recuresevely case tp: ErrorType => - s"" + s"" case tp: WildcardType => if (tp.optBounds.exists) "" else "" case NoType => @@ -286,7 +286,7 @@ class PlainPrinter(_ctx: Context) extends Printer { } "LazyRef(" ~ refTxt ~ ")" case Range(lo, hi) => - toText(lo) ~ " .. " ~ toText(hi) + toText(lo) ~ ".." ~ toText(hi) case _ => tp.fallbackToText(this) } @@ -698,8 +698,9 @@ class PlainPrinter(_ctx: Context) extends Printer { Text(ups.map(toText), ", ") Text(deps, "\n") } + val depsText = if Config.showConstraintDeps then c.depsToString else "" //Printer.debugPrintUnique = false - Text.lines(List(uninstVarsText, constrainedText, boundsText, orderingText)) + Text.lines(List(uninstVarsText, constrainedText, boundsText, orderingText, depsText)) finally ctx.typerState.constraint = savedConstraint diff --git a/tests/pos-with-compiler-cc/dotc/printing/RefinedPrinter.scala b/tests/pos-with-compiler-cc/dotc/printing/RefinedPrinter.scala index 62e1cd5baec8..9a4b53d4112c 100644 --- a/tests/pos-with-compiler-cc/dotc/printing/RefinedPrinter.scala +++ b/tests/pos-with-compiler-cc/dotc/printing/RefinedPrinter.scala @@ -223,6 +223,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case _ => val tsym = tycon.typeSymbol if tycon.isRepeatedParam then toTextLocal(args.head) ~ "*" + else if tp.isConvertibleParam then "into " ~ toText(args.head) else if defn.isFunctionSymbol(tsym) then toTextFunction(args, tsym.name.isContextFunction, tsym.name.isErasedFunction, isPure = Feature.pureFunsEnabled && !tsym.name.isImpureFunction) @@ -523,9 +524,10 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case SeqLiteral(elems, elemtpt) => "[" ~ toTextGlobal(elems, ",") ~ " : " ~ toText(elemtpt) ~ "]" case tree @ Inlined(call, bindings, body) => - (("/* inlined from " ~ (if (call.isEmpty) "outside" else toText(call)) ~ " */ ") `provided` - !homogenizedView && ctx.settings.XprintInline.value) ~ - (if bindings.isEmpty then toText(body) else blockText(bindings :+ body)) + val bodyText = if bindings.isEmpty then toText(body) else blockText(bindings :+ body) + if homogenizedView || !ctx.settings.XprintInline.value then bodyText + else if call.isEmpty then stringText("{{") ~ stringText("/* inlined from outside */") ~ bodyText ~ stringText("}}") + else keywordText("{{") ~ keywordText("/* inlined from ") ~ toText(call) ~ keywordText(" */") ~ bodyText ~ keywordText("}}") case tpt: untpd.DerivedTypeTree => "" case TypeTree() => @@ -1060,7 +1062,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { if (sym.isImport) sym.infoOrCompleter match { case info: Namer#Completer => return info.original.show - case info: ImportType => return s"import $info.expr.show" + case info: ImportType => return s"import ${info.expr.show}" case _ => } def name = diff --git a/tests/pos-with-compiler-cc/dotc/quoted/Interpreter.scala b/tests/pos-with-compiler-cc/dotc/quoted/Interpreter.scala index 5a9490c3723e..6c3d03fad4ca 100644 --- a/tests/pos-with-compiler-cc/dotc/quoted/Interpreter.scala +++ b/tests/pos-with-compiler-cc/dotc/quoted/Interpreter.scala @@ -28,6 +28,7 @@ import dotty.tools.dotc.quoted._ import dotty.tools.dotc.transform.TreeMapWithStages._ import dotty.tools.dotc.typer.ImportInfo.withRootImports import dotty.tools.dotc.util.SrcPos +import dotty.tools.dotc.reporting.Message import dotty.tools.repl.AbstractFileClassLoader /** Tree interpreter for metaprogramming constructs */ @@ -46,7 +47,7 @@ abstract class Interpreter(pos: SrcPos, classLoader: ClassLoader)(using Context) case obj: T => Some(obj) case obj => // TODO upgrade to a full type tag check or something similar - report.error(s"Interpreted tree returned a result of an unexpected type. Expected ${ct.runtimeClass} but was ${obj.getClass}", pos) + report.error(em"Interpreted tree returned a result of an unexpected type. Expected ${ct.runtimeClass} but was ${obj.getClass}", pos) None } @@ -190,7 +191,7 @@ abstract class Interpreter(pos: SrcPos, classLoader: ClassLoader)(using Context) } private def unexpectedTree(tree: Tree)(implicit env: Env): Object = - throw new StopInterpretation("Unexpected tree could not be interpreted: " + tree, tree.srcPos) + throw new StopInterpretation(em"Unexpected tree could not be interpreted: ${tree.toString}", tree.srcPos) private def loadModule(sym: Symbol): Object = if (sym.owner.is(Package)) { @@ -249,7 +250,7 @@ abstract class Interpreter(pos: SrcPos, classLoader: ClassLoader)(using Context) sw.write("\n") ex.printStackTrace(new PrintWriter(sw)) sw.write("\n") - throw new StopInterpretation(sw.toString, pos) + throw new StopInterpretation(sw.toString.toMessage, pos) case ex: InvocationTargetException => ex.getTargetException match { case ex: scala.quoted.runtime.StopMacroExpansion => @@ -270,7 +271,7 @@ abstract class Interpreter(pos: SrcPos, classLoader: ClassLoader)(using Context) } targetException.printStackTrace(new PrintWriter(sw)) sw.write("\n") - throw new StopInterpretation(sw.toString, pos) + throw new StopInterpretation(sw.toString.toMessage, pos) } } @@ -344,7 +345,7 @@ end Interpreter object Interpreter: /** Exception that stops interpretation if some issue is found */ - class StopInterpretation(val msg: String, val pos: SrcPos) extends Exception + class StopInterpretation(val msg: Message, val pos: SrcPos) extends Exception object Call: import tpd._ diff --git a/tests/pos-with-compiler-cc/dotc/quoted/PickledQuotes.scala b/tests/pos-with-compiler-cc/dotc/quoted/PickledQuotes.scala index 41f3fd4f64f3..614ec8b11c2e 100644 --- a/tests/pos-with-compiler-cc/dotc/quoted/PickledQuotes.scala +++ b/tests/pos-with-compiler-cc/dotc/quoted/PickledQuotes.scala @@ -12,7 +12,7 @@ import dotty.tools.dotc.core.tasty.{ PositionPickler, TastyPickler, TastyPrinter import dotty.tools.dotc.core.tasty.DottyUnpickler import dotty.tools.dotc.core.tasty.TreeUnpickler.UnpickleMode import dotty.tools.dotc.report - +import dotty.tools.dotc.reporting.Message import scala.quoted.Quotes import scala.quoted.runtime.impl._ @@ -220,7 +220,7 @@ object PickledQuotes { treePkl.pickle(tree :: Nil) treePkl.compactify() if tree.span.exists then - val positionWarnings = new mutable.ListBuffer[String]() + val positionWarnings = new mutable.ListBuffer[Message]() val reference = ctx.settings.sourceroot.value new PositionPickler(pickler, treePkl.buf.addrOfTree, treePkl.treeAnnots, reference) .picklePositions(ctx.compilationUnit.source, tree :: Nil, positionWarnings) diff --git a/tests/pos-with-compiler-cc/dotc/report.scala b/tests/pos-with-compiler-cc/dotc/report.scala index 636da444696c..f7e3b7b49b39 100644 --- a/tests/pos-with-compiler-cc/dotc/report.scala +++ b/tests/pos-with-compiler-cc/dotc/report.scala @@ -26,30 +26,18 @@ object report: def deprecationWarning(msg: Message, pos: SrcPos)(using Context): Unit = issueWarning(new DeprecationWarning(msg, pos.sourcePos)) - def deprecationWarning(msg: -> String, pos: SrcPos)(using Context): Unit = - deprecationWarning(msg.toMessage, pos) - def migrationWarning(msg: Message, pos: SrcPos)(using Context): Unit = issueWarning(new MigrationWarning(msg, pos.sourcePos)) - def migrationWarning(msg: -> String, pos: SrcPos)(using Context): Unit = - migrationWarning(msg.toMessage, pos) - def uncheckedWarning(msg: Message, pos: SrcPos)(using Context): Unit = issueWarning(new UncheckedWarning(msg, pos.sourcePos)) - def uncheckedWarning(msg: -> String, pos: SrcPos)(using Context): Unit = - uncheckedWarning(msg.toMessage, pos) - def featureWarning(msg: Message, pos: SrcPos)(using Context): Unit = issueWarning(new FeatureWarning(msg, pos.sourcePos)) - def featureWarning(msg: -> String, pos: SrcPos)(using Context): Unit = - featureWarning(msg.toMessage, pos) - def featureWarning(feature: String, featureDescription: -> String, - featureUseSite: Symbol, required: Boolean, pos: SrcPos)(using Context): Unit = { - val req = if (required) "needs to" else "should" + featureUseSite: Symbol, required: Boolean, pos: SrcPos)(using Context): Unit = + val req = if required then "needs to" else "should" val fqname = s"scala.language.$feature" val explain = @@ -60,27 +48,33 @@ object report: |See the Scala docs for value $fqname for a discussion |why the feature $req be explicitly enabled.""".stripMargin - def msg = s"""$featureDescription $req be enabled - |by adding the import clause 'import $fqname' - |or by setting the compiler option -language:$feature.$explain""".stripMargin - if (required) error(msg, pos) - else issueWarning(new FeatureWarning(msg.toMessage, pos.sourcePos)) - } + def msg = em"""$featureDescription $req be enabled + |by adding the import clause 'import $fqname' + |or by setting the compiler option -language:$feature.$explain""" + if required then error(msg, pos) + else issueWarning(new FeatureWarning(msg, pos.sourcePos)) + end featureWarning def warning(msg: Message, pos: SrcPos)(using Context): Unit = issueWarning(new Warning(msg, addInlineds(pos))) + def warning(msg: Message)(using Context): Unit = + warning(msg, NoSourcePosition) + def warning(msg: -> String, pos: SrcPos = NoSourcePosition)(using Context): Unit = warning(msg.toMessage, pos) - def error(msg: Message, pos: SrcPos)(using Context): Unit = + def error(msg: Message, pos: SrcPos = NoSourcePosition)(using Context): Unit = val fullPos = addInlineds(pos) ctx.reporter.report(new Error(msg, fullPos)) if ctx.settings.YdebugError.value then Thread.dumpStack() - def error(msg: -> String, pos: SrcPos = NoSourcePosition)(using Context): Unit = + def error(msg: -> String, pos: SrcPos)(using Context): Unit = error(msg.toMessage, pos) + def error(msg: -> String)(using Context): Unit = + error(msg, NoSourcePosition) + def error(ex: TypeError, pos: SrcPos)(using Context): Unit = val fullPos = addInlineds(pos) ctx.reporter.report(new StickyError(ex.toMessage, fullPos)) @@ -91,16 +85,10 @@ object report: if sourceVersion.isMigrating && sourceVersion.ordinal <= from.ordinal then migrationWarning(msg, pos) else error(msg, pos) - def errorOrMigrationWarning(msg: -> String, pos: SrcPos, from: SourceVersion)(using Context): Unit = - errorOrMigrationWarning(msg.toMessage, pos, from) - def gradualErrorOrMigrationWarning(msg: Message, pos: SrcPos, warnFrom: SourceVersion, errorFrom: SourceVersion)(using Context): Unit = if sourceVersion.isAtLeast(errorFrom) then errorOrMigrationWarning(msg, pos, errorFrom) else if sourceVersion.isAtLeast(warnFrom) then warning(msg, pos) - def gradualErrorOrMigrationWarning(msg: -> String, pos: SrcPos, warnFrom: SourceVersion, errorFrom: SourceVersion)(using Context): Unit = - gradualErrorOrMigrationWarning(msg.toMessage, pos, warnFrom, errorFrom) - def restrictionError(msg: Message, pos: SrcPos = NoSourcePosition)(using Context): Unit = error(msg.mapMsg("Implementation restriction: " + _), pos) diff --git a/tests/pos-with-compiler-cc/dotc/reporting/ErrorMessageID.scala b/tests/pos-with-compiler-cc/dotc/reporting/ErrorMessageID.scala index d9140a6309b8..a7bc7f027517 100644 --- a/tests/pos-with-compiler-cc/dotc/reporting/ErrorMessageID.scala +++ b/tests/pos-with-compiler-cc/dotc/reporting/ErrorMessageID.scala @@ -176,7 +176,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe case JavaEnumParentArgsID // errorNumber: 160 case AlreadyDefinedID // errorNumber: 161 case CaseClassInInlinedCodeID // errorNumber: 162 - case OverrideTypeMismatchErrorID // errorNumber: 163 + case OverrideTypeMismatchErrorID extends ErrorMessageID(isActive = false) // errorNumber: 163 case OverrideErrorID // errorNumber: 164 case MatchableWarningID // errorNumber: 165 case CannotExtendFunctionID // errorNumber: 166 @@ -185,6 +185,8 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe case TargetNameOnTopLevelClassID // errorNumber: 169 case NotClassTypeID // errorNumber 170 case MissingArgumentID // errorNumer 171 + case MissingImplicitArgumentID // errorNumber 172 + case CannotBeAccessedID // errorNumber 173 def errorNumber = ordinal - 1 diff --git a/tests/pos-with-compiler-cc/dotc/reporting/Message.scala b/tests/pos-with-compiler-cc/dotc/reporting/Message.scala index 62ee4c54c354..30fa10e202f2 100644 --- a/tests/pos-with-compiler-cc/dotc/reporting/Message.scala +++ b/tests/pos-with-compiler-cc/dotc/reporting/Message.scala @@ -2,16 +2,36 @@ package dotty.tools package dotc package reporting -import core.Contexts.*, core.Decorators.*, core.Mode +import core.* +import Contexts.*, Decorators.*, Symbols.*, Types.*, Flags.* +import printing.{RefinedPrinter, MessageLimiter, ErrorMessageLimiter} +import printing.Texts.Text +import printing.Formatting.hl import config.SourceVersion + import scala.language.unsafeNulls + import scala.annotation.threadUnsafe import language.experimental.pureFunctions -object Message { - val nonSensicalStartTag: String = "" - val nonSensicalEndTag: String = "" - +/** ## Tips for error message generation + * + * - You can use the `em` interpolator for error messages. It's defined in core.Decorators. + * - You can also use a simple string argument for `error` or `warning` (not for the other variants), + * but the string should not be interpolated or composed of objects that require a + * Context for evaluation. + * - When embedding interpolated substrings defined elsewhere in error messages, + * use `i` and make sure they are defined as def's instead of vals. That way, the + * possibly expensive interpolation will performed only in the case where the message + * is eventually printed. Note: At least during typer, it's common for messages + * to be discarded without being printed. Also, by making them defs, you ensure that + * they will be evaluated in the Message context, which makes formatting safer + * and more robust. + * - For common messages, or messages that might require explanation, prefer defining + * a new `Message` class in file `messages.scala` and use that instead. The advantage is that these + * messages have unique IDs that can be referenced elsewhere. + */ +object Message: def rewriteNotice(what: String, version: SourceVersion | Null = null, options: String = "")(using Context): String = if !ctx.mode.is(Mode.Interactive) then val sourceStr = if version != null then i"-source $version" else "" @@ -21,7 +41,188 @@ object Message { else i"$sourceStr $options" i"\n$what can be rewritten automatically under -rewrite $optionStr." else "" -} + + private type Recorded = Symbol | ParamRef | SkolemType + + private case class SeenKey(str: String, isType: Boolean) + + /** A class that records printed items of one of the types in `Recorded`, + * adds superscripts for disambiguations, and can explain recorded symbols + * in ` where` clause + */ + private class Seen(disambiguate: Boolean): + + private val seen = new collection.mutable.HashMap[SeenKey, List[Recorded]]: + override def default(key: SeenKey) = Nil + + var nonSensical = false + + /** If false, stop all recordings */ + private var recordOK = disambiguate + + /** Clear all entries and stop further entries to be added */ + def disable() = + seen.clear() + recordOK = false + + /** Record an entry `entry` with given String representation `str` and a + * type/term namespace identified by `isType`. + * If the entry was not yet recorded, allocate the next superscript corresponding + * to the same string in the same name space. The first recording is the string proper + * and following recordings get consecutive superscripts starting with 2. + * @return The possibly superscripted version of `str`. + */ + def record(str: String, isType: Boolean, entry: Recorded)(using Context): String = + if !recordOK then return str + //println(s"recording $str, $isType, $entry") + + /** If `e1` is an alias of another class of the same name, return the other + * class symbol instead. This normalization avoids recording e.g. scala.List + * and scala.collection.immutable.List as two different types + */ + def followAlias(e1: Recorded): Recorded = e1 match { + case e1: Symbol if e1.isAliasType => + val underlying = e1.typeRef.underlyingClassRef(refinementOK = false).typeSymbol + if (underlying.name == e1.name) underlying else e1 + case _ => e1 + } + val key = SeenKey(str, isType) + val existing = seen(key) + lazy val dealiased = followAlias(entry) + + // alts: The alternatives in `existing` that are equal, or follow (an alias of) `entry` + var alts = existing.dropWhile(alt => dealiased ne followAlias(alt)) + if alts.isEmpty then + alts = entry :: existing + seen(key) = alts + + val suffix = alts.length match { + case 1 => "" + case n => n.toString.toCharArray.map { + case '0' => '⁰' + case '1' => '¹' + case '2' => '²' + case '3' => '³' + case '4' => '⁴' + case '5' => '⁵' + case '6' => '⁶' + case '7' => '⁷' + case '8' => '⁸' + case '9' => '⁹' + }.mkString + } + str + suffix + end record + + /** Create explanation for single `Recorded` type or symbol */ + private def explanation(entry: AnyRef)(using Context): String = + def boundStr(bound: Type, default: ClassSymbol, cmp: String) = + if (bound.isRef(default)) "" else i"$cmp $bound" + + def boundsStr(bounds: TypeBounds): String = { + val lo = boundStr(bounds.lo, defn.NothingClass, ">:") + val hi = boundStr(bounds.hi, defn.AnyClass, "<:") + if (lo.isEmpty) hi + else if (hi.isEmpty) lo + else s"$lo and $hi" + } + + def addendum(cat: String, info: Type): String = info match { + case bounds @ TypeBounds(lo, hi) if bounds ne TypeBounds.empty => + if (lo eq hi) i" which is an alias of $lo" + else i" with $cat ${boundsStr(bounds)}" + case _ => + "" + } + + entry match { + case param: TypeParamRef => + s"is a type variable${addendum("constraint", TypeComparer.bounds(param))}" + case param: TermParamRef => + s"is a reference to a value parameter" + case sym: Symbol => + val info = + if (ctx.gadt.contains(sym)) + sym.info & ctx.gadt.fullBounds(sym) + else + sym.info + s"is a ${ctx.printer.kindString(sym)}${sym.showExtendedLocation}${addendum("bounds", info)}" + case tp: SkolemType => + s"is an unknown value of type ${tp.widen.show}" + } + end explanation + + /** Produce a where clause with explanations for recorded iterms. + */ + def explanations(using Context): String = + def needsExplanation(entry: Recorded) = entry match { + case param: TypeParamRef => ctx.typerState.constraint.contains(param) + case param: ParamRef => false + case skolem: SkolemType => true + case sym: Symbol => + ctx.gadt.contains(sym) && ctx.gadt.fullBounds(sym) != TypeBounds.empty + } + + val toExplain: List[(String, Recorded)] = seen.toList.flatMap { kvs => + val res: List[(String, Recorded)] = kvs match { + case (key, entry :: Nil) => + if (needsExplanation(entry)) (key.str, entry) :: Nil else Nil + case (key, entries) => + for (alt <- entries) yield { + val tickedString = record(key.str, key.isType, alt) + (tickedString, alt) + } + } + res // help the inferrencer out + }.sortBy(_._1) + + def columnar(parts: List[(String, String)]): List[String] = { + lazy val maxLen = parts.map(_._1.length).max + parts.map { + case (leader, trailer) => + val variable = hl(leader) + s"""$variable${" " * (maxLen - leader.length)} $trailer""" + } + } + + val explainParts = toExplain.map { case (str, entry) => (str, explanation(entry)) } + val explainLines = columnar(explainParts) + if (explainLines.isEmpty) "" else i"where: $explainLines%\n %\n" + end explanations + end Seen + + /** Printer to be used when formatting messages */ + private class Printer(val seen: Seen, _ctx: Context) extends RefinedPrinter(_ctx): + + /** True if printer should a show source module instead of its module class */ + private def useSourceModule(sym: Symbol): Boolean = + sym.is(ModuleClass, butNot = Package) && sym.sourceModule.exists && !_ctx.settings.YdebugNames.value + + override def simpleNameString(sym: Symbol): String = + if useSourceModule(sym) then simpleNameString(sym.sourceModule) + else seen.record(super.simpleNameString(sym), sym.isType, sym) + + override def ParamRefNameString(param: ParamRef): String = + seen.record(super.ParamRefNameString(param), param.isInstanceOf[TypeParamRef], param) + + override def toTextRef(tp: SingletonType): Text = tp match + case tp: SkolemType => seen.record(tp.repr.toString, isType = true, tp) + case _ => super.toTextRef(tp) + + override def toText(tp: Type): Text = + if !tp.exists || tp.isErroneous then seen.nonSensical = true + tp match + case tp: TypeRef if useSourceModule(tp.symbol) => Str("object ") ~ super.toText(tp) + case _ => super.toText(tp) + + override def toText(sym: Symbol): Text = + sym.infoOrCompleter match + case _: ErrorType | TypeAlias(_: ErrorType) | NoType => seen.nonSensical = true + case _ => + super.toText(sym) + end Printer + +end Message /** A `Message` contains all semantic information necessary to easily * comprehend what caused the message to be logged. Each message can be turned @@ -38,9 +239,41 @@ object Message { * * @param errorId a unique id identifying the message, this will be * used to reference documentation online + * + * Messages modify the rendendering of interpolated strings in several ways: + * + * 1. The size of the printed code is limited with a MessafeLimiter. If the message + * would get too large or too deeply nested, a `...` is printed instead. + * 2. References to module classes are prefixed with `object ` for better recogniability. + * 3. A where clause is sometimes added which contains the following additional explanations: + * - Rerences are disambiguated: If a message contains occurrences of the same identifier + * representing different symbols, the duplicates are printed with superscripts + * and the where-clause explains where each symbol is located. + * - Uninstantiated variables are explained in the where-clause with additional + * info about their bounds. + * - Skolems are explained with additional info about their underlying type. + * + * Messages inheriting from the NoDisambiguation trait or returned from the + * `noDisambiguation()` method skip point (3) above. This makes sense if the + * message already exolains where different occurrences of the same identifier + * are located. Examples are NamingMsgs such as double definition errors, + * overriding errors, and ambiguous implicit errors. + * + * We consciously made the design decision to disambiguate by default and disable + * disambiguation as an opt-in. The reason is that one usually does not consider all + * fine-grained details when writing an error message. If disambiguation is the default, + * some tests will show where clauses that look too noisy and that then can be disabled + * when needed. But if silence is the default, one usually does not realize that + * better info could be obtained by turning disambiguation on. */ -abstract class Message(val errorId: ErrorMessageID) { self => - import Message._ +abstract class Message(val errorId: ErrorMessageID)(using Context) { self => + import Message.* + + /** The kind of the error message, e.g. "Syntax" or "Type Mismatch". + * This will be printed as "$kind Error", "$kind Warning", etc, on the first + * line of the message. + */ + def kind: MessageKind /** The `msg` contains the diagnostic message e.g: * @@ -51,22 +284,27 @@ abstract class Message(val errorId: ErrorMessageID) { self => * `Diagnostic`. The message is given in raw form, with possible embedded * tags. */ - protected def msg: String - - /** The kind of the error message, e.g. "Syntax" or "Type Mismatch". - * This will be printed as "$kind Error", "$kind Warning", etc, on the first - * line of the message. - */ - def kind: MessageKind + protected def msg(using Context): String /** The explanation should provide a detailed description of why the error * occurred and use examples from the user's own code to illustrate how to * avoid these errors. It might contain embedded tags. */ - protected def explain: String + protected def explain(using Context): String - /** A message suffix that can be added for certain subclasses */ - protected def msgSuffix: String = "" + /** What gets printed after the message proper */ + protected def msgPostscript(using Context): String = + if ctx eq NoContext then "" + else ctx.printer match + case msgPrinter: Message.Printer => + myIsNonSensical = msgPrinter.seen.nonSensical + val addendum = msgPrinter.seen.explanations + msgPrinter.seen.disable() + // Clear entries and stop futher recording so that messages containing the current + // one don't repeat the explanations or use explanations from the msgPostscript. + if addendum.isEmpty then "" else "\n\n" ++ addendum + case _ => + "" /** Does this message have an explanation? * This is normally the same as `explain.nonEmpty` but can be overridden @@ -75,61 +313,69 @@ abstract class Message(val errorId: ErrorMessageID) { self => */ def canExplain: Boolean = explain.nonEmpty - private var myMsg: String | Null = null private var myIsNonSensical: Boolean = false - private def dropNonSensical(msg: String): String = - if msg.contains(nonSensicalStartTag) then - myIsNonSensical = true - // myMsg might be composed of several d"..." invocations -> nested - // nonsensical tags possible - msg - .replace(nonSensicalStartTag, "") - .replace(nonSensicalEndTag, "") - else msg + /** A message is non-sensical if it contains references to internally + * generated error types. Normally we want to suppress error messages + * referring to types like this because they look weird and are normally + * follow-up errors to something that was diagnosed before. + */ + def isNonSensical: Boolean = { message; myIsNonSensical } + + private var disambiguate: Boolean = true + + def withoutDisambiguation(): this.type = + disambiguate = false + this - /** The message with potential embedded tags */ - def rawMessage = message + private def inMessageContext(disambiguate: Boolean)(op: Context ?=> String): String = + if ctx eq NoContext then op + else + val msgContext = ctx.printer match + case _: Message.Printer => ctx + case _ => + val seen = Seen(disambiguate) + val ctx1 = ctx.fresh.setPrinterFn(Message.Printer(seen, _)) + if !ctx1.property(MessageLimiter).isDefined then + ctx1.setProperty(MessageLimiter, ErrorMessageLimiter()) + ctx1 + op(using msgContext) /** The message to report. tags are filtered out */ - @threadUnsafe lazy val message: String = dropNonSensical(msg + msgSuffix) + @threadUnsafe lazy val message: String = + inMessageContext(disambiguate)(msg + msgPostscript) /** The explanation to report. tags are filtered out */ - @threadUnsafe lazy val explanation: String = dropNonSensical(explain) - - /** A message is non-sensical if it contains references to - * tags. Such tags are inserted by the error diagnostic framework if a - * message contains references to internally generated error types. Normally - * we want to suppress error messages referring to types like this because - * they look weird and are normally follow-up errors to something that was - * diagnosed before. - */ - def isNonSensical: Boolean = { message; myIsNonSensical } + @threadUnsafe lazy val explanation: String = + inMessageContext(disambiguate = false)(explain) /** The implicit `Context` in messages is a large thing that we don't want * persisted. This method gets around that by duplicating the message, * forcing its `msg` and `explanation` vals and dropping the implicit context * that was captured in the original message. */ - def persist: Message = new Message(errorId) { - val kind = self.kind - val msg = self.msg - val explain = self.explain + def persist: Message = new Message(errorId)(using NoContext): + val kind = self.kind + private val persistedMsg = self.message + private val persistedExplain = self.explanation + def msg(using Context) = persistedMsg + def explain(using Context) = persistedExplain override val canExplain = self.canExplain - } + override def isNonSensical = self.isNonSensical def append(suffix: -> String): Message = mapMsg(_ ++ suffix) + def prepend(prefix: -> String): Message = mapMsg(prefix ++ _) def mapMsg(f: String -> String): Message = new Message(errorId): - val kind = self.kind - def msg = f(self.msg) - def explain = self.explain + val kind = self.kind + def msg(using Context) = f(self.msg) + def explain(using Context) = self.explain override def canExplain = self.canExplain def appendExplanation(suffix: -> String): Message = new Message(errorId): - val kind = self.kind - def msg = self.msg - def explain = self.explain ++ suffix + val kind = self.kind + def msg(using Context) = self.msg + def explain(using Context) = self.explain ++ suffix override def canExplain = true /** Override with `true` for messages that should always be shown even if their @@ -142,10 +388,14 @@ abstract class Message(val errorId: ErrorMessageID) { self => override def toString = msg } +/** A marker trait that suppresses generation of `where` clause for disambiguations */ +trait NoDisambiguation extends Message: + withoutDisambiguation() + /** The fallback `Message` containing no explanation and having no `kind` */ -class NoExplanation(msgFn: -> String) extends Message(ErrorMessageID.NoExplanationID) { - def msg: String = msgFn - def explain: String = "" +final class NoExplanation(msgFn: Context ?-> String)(using Context) extends Message(ErrorMessageID.NoExplanationID) { + def msg(using Context): String = msgFn + def explain(using Context): String = "" val kind: MessageKind = MessageKind.NoKind override def toString(): String = msg diff --git a/tests/pos-with-compiler-cc/dotc/reporting/Reporter.scala b/tests/pos-with-compiler-cc/dotc/reporting/Reporter.scala index 2cb9ce50cbbe..a72a7eb411a9 100644 --- a/tests/pos-with-compiler-cc/dotc/reporting/Reporter.scala +++ b/tests/pos-with-compiler-cc/dotc/reporting/Reporter.scala @@ -10,11 +10,11 @@ import dotty.tools.dotc.core.Symbols.{NoSymbol, Symbol} import dotty.tools.dotc.reporting.Diagnostic._ import dotty.tools.dotc.reporting.Message._ import dotty.tools.dotc.util.NoSourcePosition -import core.Decorators.toMessage import java.io.{BufferedReader, PrintWriter} import scala.annotation.internal.sharable import scala.collection.mutable +import core.Decorators.em import scala.caps.unsafe.unsafeUnbox import language.experimental.pureFunctions @@ -31,9 +31,9 @@ object Reporter { override def report(dia: Diagnostic)(using Context): Unit = () } - type ErrorHandler = (Diagnostic, Context) => Unit + type ErrorHandler = (Diagnostic, Context) -> Unit - private val defaultIncompleteHandler: (Diagnostic, Context) -> Unit = + private val defaultIncompleteHandler: ErrorHandler = (mc, ctx) => ctx.reporter.report(mc)(using ctx) /** Show prompt if `-Xprompt` is passed as a flag to the compiler */ @@ -221,8 +221,8 @@ abstract class Reporter extends interfaces.ReporterResult { def summarizeUnreportedWarnings()(using Context): Unit = for (settingName, count) <- unreportedWarnings do val were = if count == 1 then "was" else "were" - val msg = s"there $were ${countString(count, settingName.tail + " warning")}; re-run with $settingName for details" - report(Warning(msg.toMessage, NoSourcePosition)) + val msg = em"there $were ${countString(count, settingName.tail + " warning")}; re-run with $settingName for details" + report(Warning(msg, NoSourcePosition)) /** Print the summary of warnings and errors */ def printSummary()(using Context): Unit = { diff --git a/tests/pos-with-compiler-cc/dotc/reporting/WConf.scala b/tests/pos-with-compiler-cc/dotc/reporting/WConf.scala index 21e10e894e0b..af1a5c0f0f47 100644 --- a/tests/pos-with-compiler-cc/dotc/reporting/WConf.scala +++ b/tests/pos-with-compiler-cc/dotc/reporting/WConf.scala @@ -18,7 +18,7 @@ enum MessageFilter: case Feature => message.isInstanceOf[Diagnostic.FeatureWarning] case Unchecked => message.isInstanceOf[Diagnostic.UncheckedWarning] case MessagePattern(pattern) => - val noHighlight = message.msg.rawMessage.replaceAll("\\e\\[[\\d;]*[^\\d;]","") + val noHighlight = message.msg.message.replaceAll("\\e\\[[\\d;]*[^\\d;]","") pattern.findFirstIn(noHighlight).nonEmpty case MessageID(errorId) => message.msg.errorId == errorId case None => false diff --git a/tests/pos-with-compiler-cc/dotc/reporting/messages.scala b/tests/pos-with-compiler-cc/dotc/reporting/messages.scala index 1e85a1c917b1..ab8c3294fbb2 100644 --- a/tests/pos-with-compiler-cc/dotc/reporting/messages.scala +++ b/tests/pos-with-compiler-cc/dotc/reporting/messages.scala @@ -15,9 +15,10 @@ import printing.Formatting import ErrorMessageID._ import ast.Trees import config.{Feature, ScalaVersion} -import typer.ErrorReporting.{err, matchReductionAddendum} +import typer.ErrorReporting.{err, matchReductionAddendum, substitutableTypeSymbolsInScope} import typer.ProtoTypes.ViewProto -import typer.Implicits.Candidate +import typer.Implicits.* +import typer.Inferencing import scala.util.control.NonFatal import StdNames.nme import printing.Formatting.hl @@ -25,6 +26,8 @@ import ast.Trees._ import ast.untpd import ast.tpd import transform.SymUtils._ +import scala.util.matching.Regex +import java.util.regex.Matcher.quoteReplacement import cc.CaptureSet.IdentityCaptRefMap import language.experimental.pureFunctions @@ -41,211 +44,212 @@ import language.experimental.pureFunctions * ``` */ - abstract class SyntaxMsg(errorId: ErrorMessageID) extends Message(errorId): - def kind = MessageKind.Syntax +abstract class SyntaxMsg(errorId: ErrorMessageID)(using Context) extends Message(errorId): + def kind = MessageKind.Syntax - abstract class TypeMsg(errorId: ErrorMessageID) extends Message(errorId): - def kind = MessageKind.Type +abstract class TypeMsg(errorId: ErrorMessageID)(using Context) extends Message(errorId): + def kind = MessageKind.Type - trait ShowMatchTrace(tps: Type*)(using Context) extends Message: - override def msgSuffix: String = matchReductionAddendum(tps*) +trait ShowMatchTrace(tps: Type*)(using Context) extends Message: + override def msgPostscript(using Context): String = + super.msgPostscript ++ matchReductionAddendum(tps*) - abstract class TypeMismatchMsg(found: Type, expected: Type)(errorId: ErrorMessageID)(using Context) - extends Message(errorId), ShowMatchTrace(found, expected): - def kind = MessageKind.TypeMismatch - def explain = err.whyNoMatchStr(found, expected) - override def canExplain = true +abstract class TypeMismatchMsg(found: Type, expected: Type)(errorId: ErrorMessageID)(using Context) +extends Message(errorId), ShowMatchTrace(found, expected): + def kind = MessageKind.TypeMismatch + def explain(using Context) = err.whyNoMatchStr(found, expected) + override def canExplain = true - abstract class NamingMsg(errorId: ErrorMessageID) extends Message(errorId): - def kind = MessageKind.Naming +abstract class NamingMsg(errorId: ErrorMessageID)(using Context) extends Message(errorId), NoDisambiguation: + def kind = MessageKind.Naming - abstract class DeclarationMsg(errorId: ErrorMessageID) extends Message(errorId): - def kind = MessageKind.Declaration +abstract class DeclarationMsg(errorId: ErrorMessageID)(using Context) extends Message(errorId): + def kind = MessageKind.Declaration - /** A simple not found message (either for idents, or member selection. - * Messages of this class are sometimes dropped in favor of other, more - * specific messages. - */ - abstract class NotFoundMsg(errorId: ErrorMessageID) extends Message(errorId): - def kind = MessageKind.NotFound - def name: Name +/** A simple not found message (either for idents, or member selection. + * Messages of this class are sometimes dropped in favor of other, more + * specific messages. + */ +abstract class NotFoundMsg(errorId: ErrorMessageID)(using Context) extends Message(errorId): + def kind = MessageKind.NotFound + def name: Name - abstract class PatternMatchMsg(errorId: ErrorMessageID) extends Message(errorId): - def kind = MessageKind.PatternMatch +abstract class PatternMatchMsg(errorId: ErrorMessageID)(using Context) extends Message(errorId): + def kind = MessageKind.PatternMatch - abstract class CyclicMsg(errorId: ErrorMessageID) extends Message(errorId): - def kind = MessageKind.Cyclic +abstract class CyclicMsg(errorId: ErrorMessageID)(using Context) extends Message(errorId): + def kind = MessageKind.Cyclic - abstract class ReferenceMsg(errorId: ErrorMessageID) extends Message(errorId): - def kind = MessageKind.Reference +abstract class ReferenceMsg(errorId: ErrorMessageID)(using Context) extends Message(errorId): + def kind = MessageKind.Reference - abstract class EmptyCatchOrFinallyBlock(tryBody: untpd.Tree, errNo: ErrorMessageID)(using Context) - extends SyntaxMsg(errNo) { - def explain = { - val tryString = tryBody match { - case Block(Nil, untpd.EmptyTree) => "{}" - case _ => tryBody.show - } - - val code1 = - s"""|import scala.util.control.NonFatal - | - |try $tryString catch { - | case NonFatal(e) => ??? - |}""".stripMargin - - val code2 = - s"""|try $tryString finally { - | // perform your cleanup here! - |}""".stripMargin - - em"""|A ${hl("try")} expression should be followed by some mechanism to handle any exceptions - |thrown. Typically a ${hl("catch")} expression follows the ${hl("try")} and pattern matches - |on any expected exceptions. For example: - | - |$code1 - | - |It is also possible to follow a ${hl("try")} immediately by a ${hl("finally")} - letting the - |exception propagate - but still allowing for some clean up in ${hl("finally")}: - | - |$code2 - | - |It is recommended to use the ${hl("NonFatal")} extractor to catch all exceptions as it - |correctly handles transfer functions like ${hl("return")}.""" +abstract class EmptyCatchOrFinallyBlock(tryBody: untpd.Tree, errNo: ErrorMessageID)(using Context) +extends SyntaxMsg(errNo) { + def explain(using Context) = { + val tryString = tryBody match { + case Block(Nil, untpd.EmptyTree) => "{}" + case _ => tryBody.show } - } - - class EmptyCatchBlock(tryBody: untpd.Tree)(using Context) - extends EmptyCatchOrFinallyBlock(tryBody, EmptyCatchBlockID) { - def msg = - em"""|The ${hl("catch")} block does not contain a valid expression, try - |adding a case like - ${hl("case e: Exception =>")} to the block""" - } - - class EmptyCatchAndFinallyBlock(tryBody: untpd.Tree)(using Context) - extends EmptyCatchOrFinallyBlock(tryBody, EmptyCatchAndFinallyBlockID) { - def msg = - em"""|A ${hl("try")} without ${hl("catch")} or ${hl("finally")} is equivalent to putting - |its body in a block; no exceptions are handled.""" - } - - class DeprecatedWithOperator()(using Context) - extends SyntaxMsg(DeprecatedWithOperatorID) { - def msg = - em"""${hl("with")} as a type operator has been deprecated; use ${hl("&")} instead""" - def explain = - em"""|Dotty introduces intersection types - ${hl("&")} types. These replace the - |use of the ${hl("with")} keyword. There are a few differences in - |semantics between intersection types and using ${hl("with")}.""" - } - - class CaseClassMissingParamList(cdef: untpd.TypeDef)(using Context) - extends SyntaxMsg(CaseClassMissingParamListID) { - def msg = - em"""|A ${hl("case class")} must have at least one parameter list""" - - def explain = - em"""|${cdef.name} must have at least one parameter list, if you would rather - |have a singleton representation of ${cdef.name}, use a "${hl("case object")}". - |Or, add an explicit ${hl("()")} as a parameter list to ${cdef.name}.""" - } - class AnonymousFunctionMissingParamType(param: untpd.ValDef, - tree: untpd.Function, - pt: Type) - (using Context) - extends TypeMsg(AnonymousFunctionMissingParamTypeID) { - def msg = { - val ofFun = - if param.name.is(WildcardParamName) - || (MethodType.syntheticParamNames(tree.args.length + 1) contains param.name) - then i" of expanded function:\n$tree" - else "" + val code1 = + s"""|import scala.util.control.NonFatal + | + |try $tryString catch { + | case NonFatal(e) => ??? + |}""".stripMargin - val inferred = - if (pt == WildcardType) "" - else i"\nWhat I could infer was: $pt" + val code2 = + s"""|try $tryString finally { + | // perform your cleanup here! + |}""".stripMargin - i"""Missing parameter type - | - |I could not infer the type of the parameter ${param.name}$ofFun.$inferred""" - } + i"""|A ${hl("try")} expression should be followed by some mechanism to handle any exceptions + |thrown. Typically a ${hl("catch")} expression follows the ${hl("try")} and pattern matches + |on any expected exceptions. For example: + | + |$code1 + | + |It is also possible to follow a ${hl("try")} immediately by a ${hl("finally")} - letting the + |exception propagate - but still allowing for some clean up in ${hl("finally")}: + | + |$code2 + | + |It is recommended to use the ${hl("NonFatal")} extractor to catch all exceptions as it + |correctly handles transfer functions like ${hl("return")}.""" + } +} + +class EmptyCatchBlock(tryBody: untpd.Tree)(using Context) +extends EmptyCatchOrFinallyBlock(tryBody, EmptyCatchBlockID) { + def msg(using Context) = + i"""|The ${hl("catch")} block does not contain a valid expression, try + |adding a case like - ${hl("case e: Exception =>")} to the block""" +} + +class EmptyCatchAndFinallyBlock(tryBody: untpd.Tree)(using Context) +extends EmptyCatchOrFinallyBlock(tryBody, EmptyCatchAndFinallyBlockID) { + def msg(using Context) = + i"""|A ${hl("try")} without ${hl("catch")} or ${hl("finally")} is equivalent to putting + |its body in a block; no exceptions are handled.""" +} + +class DeprecatedWithOperator()(using Context) +extends SyntaxMsg(DeprecatedWithOperatorID) { + def msg(using Context) = + i"""${hl("with")} as a type operator has been deprecated; use ${hl("&")} instead""" + def explain(using Context) = + i"""|Dotty introduces intersection types - ${hl("&")} types. These replace the + |use of the ${hl("with")} keyword. There are a few differences in + |semantics between intersection types and using ${hl("with")}.""" +} + +class CaseClassMissingParamList(cdef: untpd.TypeDef)(using Context) +extends SyntaxMsg(CaseClassMissingParamListID) { + def msg(using Context) = + i"""|A ${hl("case class")} must have at least one parameter list""" + + def explain(using Context) = + i"""|${cdef.name} must have at least one parameter list, if you would rather + |have a singleton representation of ${cdef.name}, use a "${hl("case object")}". + |Or, add an explicit ${hl("()")} as a parameter list to ${cdef.name}.""" +} + +class AnonymousFunctionMissingParamType(param: untpd.ValDef, + tree: untpd.Function, + pt: Type) + (using Context) +extends TypeMsg(AnonymousFunctionMissingParamTypeID) { + def msg(using Context) = { + val ofFun = + if param.name.is(WildcardParamName) + || (MethodType.syntheticParamNames(tree.args.length + 1) contains param.name) + then i" of expanded function:\n$tree" + else "" - def explain = "" - } + val inferred = + if (pt == WildcardType) "" + else i"\nWhat I could infer was: $pt" - class WildcardOnTypeArgumentNotAllowedOnNew()(using Context) - extends SyntaxMsg(WildcardOnTypeArgumentNotAllowedOnNewID) { - def msg = "Type argument must be fully defined" - def explain = - val code1: String = - """ - |object TyperDemo { - | class Team[A] - | val team = new Team[?] - |} - """.stripMargin + i"""Missing parameter type + | + |I could not infer the type of the parameter ${param.name}$ofFun.$inferred""" + } + + def explain(using Context) = "" +} + +class WildcardOnTypeArgumentNotAllowedOnNew()(using Context) +extends SyntaxMsg(WildcardOnTypeArgumentNotAllowedOnNewID) { + def msg(using Context) = "Type argument must be fully defined" + def explain(using Context) = + val code1: String = + """ + |object TyperDemo { + | class Team[A] + | val team = new Team[?] + |} + """.stripMargin - val code2: String = - """ - |object TyperDemo { - | class Team[A] - | val team = new Team[Int] - |} - """.stripMargin - em"""|Wildcard on arguments is not allowed when declaring a new type. - | - |Given the following example: - | - |$code1 - | - |You must complete all the type parameters, for instance: - | - |$code2 """ - } + val code2: String = + """ + |object TyperDemo { + | class Team[A] + | val team = new Team[Int] + |} + """.stripMargin + i"""|Wildcard on arguments is not allowed when declaring a new type. + | + |Given the following example: + | + |$code1 + | + |You must complete all the type parameters, for instance: + | + |$code2 """ +} - // Type Errors ------------------------------------------------------------ // - class DuplicateBind(bind: untpd.Bind, tree: untpd.CaseDef)(using Context) - extends NamingMsg(DuplicateBindID) { - def msg = em"duplicate pattern variable: ${bind.name}" +// Type Errors ------------------------------------------------------------ // +class DuplicateBind(bind: untpd.Bind, tree: untpd.CaseDef)(using Context) +extends NamingMsg(DuplicateBindID) { + def msg(using Context) = i"duplicate pattern variable: ${bind.name}" - def explain = { - val pat = tree.pat.show - val guard = tree.guard match { - case untpd.EmptyTree => "" - case guard => s"if ${guard.show}" - } + def explain(using Context) = { + val pat = tree.pat.show + val guard = tree.guard match + case untpd.EmptyTree => "" + case guard => s"if ${guard.show}" - val body = tree.body match { - case Block(Nil, untpd.EmptyTree) => "" - case body => s" ${body.show}" - } + val body = tree.body match { + case Block(Nil, untpd.EmptyTree) => "" + case body => s" ${body.show}" + } - val caseDef = s"case $pat$guard => $body" + val caseDef = s"case $pat$guard => $body" - em"""|For each ${hl("case")} bound variable names have to be unique. In: - | - |$caseDef - | - |${bind.name} is not unique. Rename one of the bound variables!""" - } + i"""|For each ${hl("case")} bound variable names have to be unique. In: + | + |$caseDef + | + |${bind.name} is not unique. Rename one of the bound variables!""" } +} - class MissingIdent(tree: untpd.Ident, treeKind: String, val name: Name)(using Context) - extends NotFoundMsg(MissingIdentID) { - def msg = em"Not found: $treeKind$name" - def explain = { - em"""|The identifier for `$treeKind$name` is not bound, that is, - |no declaration for this identifier can be found. - |That can happen, for example, if `$name` or its declaration has either been - |misspelt or if an import is missing.""" - } +class MissingIdent(tree: untpd.Ident, treeKind: String, val name: Name)(using Context) +extends NotFoundMsg(MissingIdentID) { + def msg(using Context) = i"Not found: $treeKind$name" + def explain(using Context) = { + i"""|The identifier for `$treeKind$name` is not bound, that is, + |no declaration for this identifier can be found. + |That can happen, for example, if `$name` or its declaration has either been + |misspelt or if an import is missing.""" } +} - class TypeMismatch(found: Type, expected: Type, inTree: Option[untpd.Tree], addenda: -> String*)(using Context) - extends TypeMismatchMsg(found, expected)(TypeMismatchID): +class TypeMismatch(found: Type, expected: Type, inTree: Option[untpd.Tree], addenda: -> String*)(using Context) + extends TypeMismatchMsg(found, expected)(TypeMismatchID): + def msg(using Context) = // replace constrained TypeParamRefs and their typevars by their bounds where possible // and the bounds are not f-bounds. // The idea is that if the bounds are also not-subtypes of each other to report @@ -273,2265 +277,2497 @@ import language.experimental.pureFunctions case _ => mapOver(tp) - def msg = - val found1 = reported(found) - reported.setVariance(-1) - val expected1 = reported(expected) - val (found2, expected2) = - if (found1 frozen_<:< expected1) || reported.fbounded then (found, expected) - else (found1, expected1) - val postScript = addenda.find(!_.isEmpty) match - case Some(p) => p - case None => - if expected.isTopType || found.isBottomType - then "" - else ctx.typer.importSuggestionAddendum(ViewProto(found.widen, expected)) - val (where, printCtx) = Formatting.disambiguateTypes(found2, expected2) - val whereSuffix = if (where.isEmpty) where else s"\n\n$where" - val (foundStr, expectedStr) = Formatting.typeDiff(found2, expected2)(using printCtx) - s"""|Found: $foundStr - |Required: $expectedStr""".stripMargin - + whereSuffix + postScript - - override def explain = - val treeStr = inTree.map(x => s"\nTree: ${x.show}").getOrElse("") - treeStr + "\n" + super.explain - - end TypeMismatch - - class NotAMember(site: Type, val name: Name, selected: String, addendum: -> String = "")(using Context) - extends NotFoundMsg(NotAMemberID), ShowMatchTrace(site) { - //println(i"site = $site, decls = ${site.decls}, source = ${site.typeSymbol.sourceFile}") //DEBUG - - def msg = { - import core.Flags._ - val maxDist = 3 // maximal number of differences to be considered for a hint - val missing = name.show - - // The symbols of all non-synthetic, non-private members of `site` - // that are of the same type/term kind as the missing member. - def candidates: Set[Symbol] = - for - bc <- site.widen.baseClasses.toSet - sym <- bc.info.decls.filter(sym => - sym.isType == name.isTypeName - && !sym.isConstructor - && !sym.flagsUNSAFE.isOneOf(Synthetic | Private)) - yield sym - - // Calculate Levenshtein distance - def distance(s1: String, s2: String): Int = - val dist = Array.ofDim[Int](s2.length + 1, s1.length + 1) - for - j <- 0 to s2.length - i <- 0 to s1.length - do - dist(j)(i) = - if j == 0 then i - else if i == 0 then j - else if s2(j - 1) == s1(i - 1) then dist(j - 1)(i - 1) - else (dist(j - 1)(i) min dist(j)(i - 1) min dist(j - 1)(i - 1)) + 1 - dist(s2.length)(s1.length) - - // A list of possible candidate symbols with their Levenstein distances - // to the name of the missing member - def closest: List[(Int, Symbol)] = candidates - .toList - .map(sym => (distance(sym.name.show, missing), sym)) - .filter((d, sym) => d <= maxDist && d < missing.length && d < sym.name.show.length) - .sortBy((d, sym) => (d, sym.name.show)) // sort by distance first, alphabetically second - - val enumClause = - if ((name eq nme.values) || (name eq nme.valueOf)) && site.classSymbol.companionClass.isEnumClass then - val kind = if name eq nme.values then i"${nme.values} array" else i"${nme.valueOf} lookup method" - // an assumption is made here that the values and valueOf methods were not generated - // because the enum defines non-singleton cases - i""" - |Although ${site.classSymbol.companionClass} is an enum, it has non-singleton cases, - |meaning a $kind is not defined""" - else - "" - - def prefixEnumClause(addendum: String) = - if enumClause.nonEmpty then s".$enumClause$addendum" else addendum - - val finalAddendum = - if addendum.nonEmpty then prefixEnumClause(addendum) - else closest match - case (d, sym) :: _ => - val siteName = site match - case site: NamedType => site.name.show - case site => i"$site" - val showName = - // Add .type to the name if it is a module - if sym.is(ModuleClass) then s"${sym.name.show}.type" - else sym.name.show - s" - did you mean $siteName.$showName?$enumClause" - case Nil => prefixEnumClause("") - - ex"$selected $name is not a member of ${site.widen}$finalAddendum" - } - - def explain = "" - } - - class EarlyDefinitionsNotSupported()(using Context) - extends SyntaxMsg(EarlyDefinitionsNotSupportedID) { - def msg = "Early definitions are not supported; use trait parameters instead" - - def explain = { - val code1 = - """|trait Logging { - | val f: File - | f.open() - | onExit(f.close()) - | def log(msg: String) = f.write(msg) - |} - | - |class B extends Logging { - | val f = new File("log.data") // triggers a NullPointerException - |} - | - |// early definition gets around the NullPointerException - |class C extends { - | val f = new File("log.data") - |} with Logging""".stripMargin - - val code2 = - """|trait Logging(f: File) { - | f.open() - | onExit(f.close()) - | def log(msg: String) = f.write(msg) - |} - | - |class C extends Logging(new File("log.data"))""".stripMargin - - em"""|Earlier versions of Scala did not support trait parameters and "early - |definitions" (also known as "early initializers") were used as an alternative. - | - |Example of old syntax: - | - |$code1 - | - |The above code can now be written as: - | - |$code2 - |""" - } - } - - class TopLevelImplicitClass(cdef: untpd.TypeDef)(using Context) - extends SyntaxMsg(TopLevelImplicitClassID) { - def msg = em"""An ${hl("implicit class")} may not be top-level""" - - def explain = { - val TypeDef(name, impl @ Template(constr0, parents, self, _)) = cdef: @unchecked - val exampleArgs = - if(constr0.termParamss.isEmpty) "..." - else constr0.termParamss(0).map(_.withMods(untpd.Modifiers()).show).mkString(", ") - def defHasBody[T] = impl.body.exists(!_.isEmpty) - val exampleBody = if (defHasBody) "{\n ...\n }" else "" - em"""|There may not be any method, member or object in scope with the same name as - |the implicit class and a case class automatically gets a companion object with - |the same name created by the compiler which would cause a naming conflict if it - |were allowed. - | | - |To resolve the conflict declare ${cdef.name} inside of an ${hl("object")} then import the class - |from the object at the use site if needed, for example: - | - |object Implicits { - | implicit class ${cdef.name}($exampleArgs)$exampleBody - |} - | - |// At the use site: - |import Implicits.${cdef.name}""" - } - } - - class ImplicitCaseClass(cdef: untpd.TypeDef)(using Context) - extends SyntaxMsg(ImplicitCaseClassID) { - def msg = em"""A ${hl("case class")} may not be defined as ${hl("implicit")}""" - - def explain = - em"""|Implicit classes may not be case classes. Instead use a plain class: - | - |implicit class ${cdef.name}... - | - |""" - } + val found1 = reported(found) + reported.setVariance(-1) + val expected1 = reported(expected) + val (found2, expected2) = + if (found1 frozen_<:< expected1) || reported.fbounded then (found, expected) + else (found1, expected1) + val (foundStr, expectedStr) = Formatting.typeDiff(found2, expected2) + i"""|Found: $foundStr + |Required: $expectedStr""" + end msg + + override def msgPostscript(using Context) = + def importSuggestions = + if expected.isTopType || found.isBottomType then "" + else ctx.typer.importSuggestionAddendum(ViewProto(found.widen, expected)) + super.msgPostscript + ++ addenda.dropWhile(_.isEmpty).headOption.getOrElse(importSuggestions) + + override def explain(using Context) = + val treeStr = inTree.map(x => s"\nTree: ${x.show}").getOrElse("") + treeStr + "\n" + super.explain + +end TypeMismatch + +class NotAMember(site: Type, val name: Name, selected: String, addendum: -> String = "")(using Context) +extends NotFoundMsg(NotAMemberID), ShowMatchTrace(site) { + //println(i"site = $site, decls = ${site.decls}, source = ${site.typeSymbol.sourceFile}") //DEBUG + + def msg(using Context) = { + import core.Flags._ + val maxDist = 3 // maximal number of differences to be considered for a hint + val missing = name.show + + // The symbols of all non-synthetic, non-private members of `site` + // that are of the same type/term kind as the missing member. + def candidates: Set[Symbol] = + for + bc <- site.widen.baseClasses.toSet + sym <- bc.info.decls.filter(sym => + sym.isType == name.isTypeName + && !sym.isConstructor + && !sym.flagsUNSAFE.isOneOf(Synthetic | Private)) + yield sym + + // Calculate Levenshtein distance + def distance(s1: String, s2: String): Int = + val dist = Array.ofDim[Int](s2.length + 1, s1.length + 1) + for + j <- 0 to s2.length + i <- 0 to s1.length + do + dist(j)(i) = + if j == 0 then i + else if i == 0 then j + else if s2(j - 1) == s1(i - 1) then dist(j - 1)(i - 1) + else (dist(j - 1)(i) min dist(j)(i - 1) min dist(j - 1)(i - 1)) + 1 + dist(s2.length)(s1.length) + + // A list of possible candidate symbols with their Levenstein distances + // to the name of the missing member + def closest: List[(Int, Symbol)] = candidates + .toList + .map(sym => (distance(sym.name.show, missing), sym)) + .filter((d, sym) => d <= maxDist && d < missing.length && d < sym.name.show.length) + .sortBy((d, sym) => (d, sym.name.show)) // sort by distance first, alphabetically second + + val enumClause = + if ((name eq nme.values) || (name eq nme.valueOf)) && site.classSymbol.companionClass.isEnumClass then + val kind = if name eq nme.values then i"${nme.values} array" else i"${nme.valueOf} lookup method" + // an assumption is made here that the values and valueOf methods were not generated + // because the enum defines non-singleton cases + i""" + |Although ${site.classSymbol.companionClass} is an enum, it has non-singleton cases, + |meaning a $kind is not defined""" + else + "" - class ImplicitClassPrimaryConstructorArity()(using Context) - extends SyntaxMsg(ImplicitClassPrimaryConstructorArityID){ - def msg = "Implicit classes must accept exactly one primary constructor parameter" - def explain = { - val example = "implicit class RichDate(date: java.util.Date)" - em"""Implicit classes may only take one non-implicit argument in their constructor. For example: + def prefixEnumClause(addendum: String) = + if enumClause.nonEmpty then s".$enumClause$addendum" else addendum + + val finalAddendum = + if addendum.nonEmpty then prefixEnumClause(addendum) + else closest match + case (d, sym) :: _ => + val siteName = site match + case site: NamedType => site.name.show + case site => i"$site" + val showName = + // Add .type to the name if it is a module + if sym.is(ModuleClass) then s"${sym.name.show}.type" + else sym.name.show + s" - did you mean $siteName.$showName?$enumClause" + case Nil => prefixEnumClause("") + + i"$selected $name is not a member of ${site.widen}$finalAddendum" + } + + def explain(using Context) = "" +} + +class EarlyDefinitionsNotSupported()(using Context) +extends SyntaxMsg(EarlyDefinitionsNotSupportedID) { + def msg(using Context) = "Early definitions are not supported; use trait parameters instead" + + def explain(using Context) = { + val code1 = + """|trait Logging { + | val f: File + | f.open() + | onExit(f.close()) + | def log(msg: String) = f.write(msg) + |} | - | $example + |class B extends Logging { + | val f = new File("log.data") // triggers a NullPointerException + |} | - |While it’s possible to create an implicit class with more than one non-implicit argument, - |such classes aren’t used during implicit lookup. - |""" - } - } - - class ObjectMayNotHaveSelfType(mdef: untpd.ModuleDef)(using Context) - extends SyntaxMsg(ObjectMayNotHaveSelfTypeID) { - def msg = em"""${hl("object")}s must not have a self ${hl("type")}""" - - def explain = { - val untpd.ModuleDef(name, tmpl) = mdef - val ValDef(_, selfTpt, _) = tmpl.self - em"""|${hl("object")}s must not have a self ${hl("type")}: - | - |Consider these alternative solutions: - | - Create a trait or a class instead of an object - | - Let the object extend a trait containing the self type: - | - | object $name extends ${selfTpt.show}""" - } - } - - class RepeatedModifier(modifier: String)(implicit ctx:Context) - extends SyntaxMsg(RepeatedModifierID) { - def msg = em"""Repeated modifier $modifier""" - - def explain = { - val code1 = em"""private private val Origin = Point(0, 0)""" - val code2 = em"""private final val Origin = Point(0, 0)""" - em"""This happens when you accidentally specify the same modifier twice. - | - |Example: - | - |$code1 - | - |instead of - | - |$code2 - | - |""" - } - } - - class InterpolatedStringError()(implicit ctx:Context) - extends SyntaxMsg(InterpolatedStringErrorID) { - def msg = "Error in interpolated string: identifier or block expected" - def explain = { - val code1 = "s\"$new Point(0, 0)\"" - val code2 = "s\"${new Point(0, 0)}\"" - em"""|This usually happens when you forget to place your expressions inside curly braces. - | - |$code1 - | - |should be written as - | - |$code2 - |""" - } - } - - class UnboundPlaceholderParameter()(implicit ctx:Context) - extends SyntaxMsg(UnboundPlaceholderParameterID) { - def msg = em"""Unbound placeholder parameter; incorrect use of ${hl("_")}""" - def explain = - em"""|The ${hl("_")} placeholder syntax was used where it could not be bound. - |Consider explicitly writing the variable binding. - | - |This can be done by replacing ${hl("_")} with a variable (eg. ${hl("x")}) - |and adding ${hl("x =>")} where applicable. - | - |Example before: - | - |${hl("{ _ }")} - | - |Example after: - | - |${hl("x => { x }")} - | - |Another common occurrence for this error is defining a val with ${hl("_")}: - | - |${hl("val a = _")} - | - |But this val definition isn't very useful, it can never be assigned - |another value. And thus will always remain uninitialized. - |Consider replacing the ${hl("val")} with ${hl("var")}: - | - |${hl("var a = _")} - | - |Note that this use of ${hl("_")} is not placeholder syntax, - |but an uninitialized var definition. - |Only fields can be left uninitialized in this manner; local variables - |must be initialized. - | - |Another occurrence for this error is self type definition. - |The ${hl("_")} can be replaced with ${hl("this")}. - | - |Example before: - | - |${hl("trait A { _: B => ... ")} - | - |Example after: - | - |${hl("trait A { this: B => ... ")} - |""" - } - - class IllegalStartSimpleExpr(illegalToken: String)(using Context) - extends SyntaxMsg(IllegalStartSimpleExprID) { - def msg = em"expression expected but ${Red(illegalToken)} found" - def explain = { - em"""|An expression cannot start with ${Red(illegalToken)}.""" - } - } + |// early definition gets around the NullPointerException + |class C extends { + | val f = new File("log.data") + |} with Logging""".stripMargin + + val code2 = + """|trait Logging(f: File) { + | f.open() + | onExit(f.close()) + | def log(msg: String) = f.write(msg) + |} + | + |class C extends Logging(new File("log.data"))""".stripMargin - class MissingReturnType()(implicit ctx:Context) - extends SyntaxMsg(MissingReturnTypeID) { - def msg = "Missing return type" - def explain = - em"""|An abstract declaration must have a return type. For example: - | - |trait Shape: - | ${hl("def area: Double")} // abstract declaration returning a Double""" + i"""|Earlier versions of Scala did not support trait parameters and "early + |definitions" (also known as "early initializers") were used as an alternative. + | + |Example of old syntax: + | + |$code1 + | + |The above code can now be written as: + | + |$code2 + |""" } - - class MissingReturnTypeWithReturnStatement(method: Symbol)(using Context) - extends SyntaxMsg(MissingReturnTypeWithReturnStatementID) { - def msg = em"$method has a return statement; it needs a result type" - def explain = - em"""|If a method contains a ${hl("return")} statement, it must have an - |explicit return type. For example: - | - |${hl("def good: Int /* explicit return type */ = return 1")}""" +} + +class TopLevelImplicitClass(cdef: untpd.TypeDef)(using Context) +extends SyntaxMsg(TopLevelImplicitClassID) { + def msg(using Context) = i"""An ${hl("implicit class")} may not be top-level""" + + def explain(using Context) = { + val TypeDef(name, impl @ Template(constr0, parents, self, _)) = cdef: @unchecked + val exampleArgs = + if(constr0.termParamss.isEmpty) "..." + else constr0.termParamss(0).map(_.withMods(untpd.Modifiers()).show).mkString(", ") + def defHasBody[T] = impl.body.exists(!_.isEmpty) + val exampleBody = if (defHasBody) "{\n ...\n }" else "" + i"""|There may not be any method, member or object in scope with the same name as + |the implicit class and a case class automatically gets a companion object with + |the same name created by the compiler which would cause a naming conflict if it + |were allowed. + | | + |To resolve the conflict declare ${cdef.name} inside of an ${hl("object")} then import the class + |from the object at the use site if needed, for example: + | + |object Implicits { + | implicit class ${cdef.name}($exampleArgs)$exampleBody + |} + | + |// At the use site: + |import Implicits.${cdef.name}""" } +} - class YieldOrDoExpectedInForComprehension()(using Context) - extends SyntaxMsg(YieldOrDoExpectedInForComprehensionID) { - def msg = em"${hl("yield")} or ${hl("do")} expected" - - def explain = - em"""|When the enumerators in a for comprehension are not placed in parentheses or - |braces, a ${hl("do")} or ${hl("yield")} statement is required after the enumerators - |section of the comprehension. - | - |You can save some keystrokes by omitting the parentheses and writing - | - |${hl("val numbers = for i <- 1 to 3 yield i")} - | - | instead of - | - |${hl("val numbers = for (i <- 1 to 3) yield i")} - | - |but the ${hl("yield")} keyword is still required. - | - |For comprehensions that simply perform a side effect without yielding anything - |can also be written without parentheses but a ${hl("do")} keyword has to be - |included. For example, - | - |${hl("for (i <- 1 to 3) println(i)")} - | - |can be written as - | - |${hl("for i <- 1 to 3 do println(i) // notice the 'do' keyword")} - | - |""" - } +class ImplicitCaseClass(cdef: untpd.TypeDef)(using Context) +extends SyntaxMsg(ImplicitCaseClassID) { + def msg(using Context) = i"""A ${hl("case class")} may not be defined as ${hl("implicit")}""" - class ProperDefinitionNotFound()(using Context) - extends Message(ProperDefinitionNotFoundID) { - def kind = MessageKind.DocComment - def msg = em"""Proper definition was not found in ${hl("@usecase")}""" - - def explain = { - val noUsecase = - "def map[B, That](f: A => B)(implicit bf: CanBuildFrom[List[A], B, That]): That" - - val usecase = - """|/** Map from List[A] => List[B] - | * - | * @usecase def map[B](f: A => B): List[B] - | */ - |def map[B, That](f: A => B)(implicit bf: CanBuildFrom[List[A], B, That]): That - |""".stripMargin - - em"""|Usecases are only supported for ${hl("def")}s. They exist because with Scala's - |advanced type-system, we sometimes end up with seemingly scary signatures. - |The usage of these methods, however, needs not be - for instance the ${hl("map")} - |function - | - |${hl("List(1, 2, 3).map(2 * _) // res: List(2, 4, 6)")} - | - |is easy to understand and use - but has a rather bulky signature: - | - |$noUsecase - | - |to mitigate this and ease the usage of such functions we have the ${hl("@usecase")} - |annotation for docstrings. Which can be used like this: - | - |$usecase - | - |When creating the docs, the signature of the method is substituted by the - |usecase and the compiler makes sure that it is valid. Because of this, you're - |only allowed to use ${hl("def")}s when defining usecases.""" - } + def explain(using Context) = + i"""|Implicit classes may not be case classes. Instead use a plain class: + | + |implicit class ${cdef.name}... + | + |""" +} + +class ImplicitClassPrimaryConstructorArity()(using Context) +extends SyntaxMsg(ImplicitClassPrimaryConstructorArityID){ + def msg(using Context) = "Implicit classes must accept exactly one primary constructor parameter" + def explain(using Context) = { + val example = "implicit class RichDate(date: java.util.Date)" + i"""Implicit classes may only take one non-implicit argument in their constructor. For example: + | + | $example + | + |While it’s possible to create an implicit class with more than one non-implicit argument, + |such classes aren’t used during implicit lookup. + |""" } +} - class ByNameParameterNotSupported(tpe: untpd.Tree)(using Context) - extends SyntaxMsg(ByNameParameterNotSupportedID) { - def msg = em"By-name parameter type ${tpe} not allowed here." - - def explain = - em"""|By-name parameters act like functions that are only evaluated when referenced, - |allowing for lazy evaluation of a parameter. - | - |An example of using a by-name parameter would look like: - |${hl("def func(f: => Boolean) = f // 'f' is evaluated when referenced within the function")} - | - |An example of the syntax of passing an actual function as a parameter: - |${hl("def func(f: (Boolean => Boolean)) = f(true)")} - | - |or: - | - |${hl("def func(f: Boolean => Boolean) = f(true)")} - | - |And the usage could be as such: - |${hl("func(bool => // do something...)")} - |""" - } +class ObjectMayNotHaveSelfType(mdef: untpd.ModuleDef)(using Context) +extends SyntaxMsg(ObjectMayNotHaveSelfTypeID) { + def msg(using Context) = i"""${hl("object")}s must not have a self ${hl("type")}""" - class WrongNumberOfTypeArgs(fntpe: Type, expectedArgs: List[ParamInfo], actual: List[untpd.Tree])(using Context) - extends SyntaxMsg(WrongNumberOfTypeArgsID) { - - private val expectedCount = expectedArgs.length - private val actualCount = actual.length - private val msgPrefix = if (actualCount > expectedCount) "Too many" else "Not enough" - - def msg = - val expectedArgString = expectedArgs - .map(_.paramName.unexpandedName.show) - .mkString("[", ", ", "]") - val actualArgString = actual.map(_.show).mkString("[", ", ", "]") - val prettyName = - try fntpe.termSymbol match - case NoSymbol => fntpe.show - case symbol => symbol.showFullName - catch case NonFatal(ex) => fntpe.show - em"""|$msgPrefix type arguments for $prettyName$expectedArgString - |expected: $expectedArgString - |actual: $actualArgString""".stripMargin - - def explain = { - val tooManyTypeParams = - """|val tuple2: (Int, String) = (1, "one") - |val list: List[(Int, String)] = List(tuple2)""".stripMargin - - if (actualCount > expectedCount) - em"""|You have supplied too many type parameters - | - |For example List takes a single type parameter (List[A]) - |If you need to hold more types in a list then you need to combine them - |into another data type that can contain the number of types you need, - |In this example one solution would be to use a Tuple: - | - |${tooManyTypeParams}""" - else - em"""|You have not supplied enough type parameters - |If you specify one type parameter then you need to specify every type parameter.""" - } + def explain(using Context) = { + val untpd.ModuleDef(name, tmpl) = mdef + val ValDef(_, selfTpt, _) = tmpl.self + i"""|${hl("object")}s must not have a self ${hl("type")}: + | + |Consider these alternative solutions: + | - Create a trait or a class instead of an object + | - Let the object extend a trait containing the self type: + | + | object $name extends ${selfTpt.show}""" } +} - class IllegalVariableInPatternAlternative(name: Name)(using Context) - extends SyntaxMsg(IllegalVariableInPatternAlternativeID) { - def msg = em"Illegal variable $name in pattern alternative" - def explain = { - val varInAlternative = - """|def g(pair: (Int,Int)): Int = pair match { - | case (1, n) | (n, 1) => n - | case _ => 0 - |}""".stripMargin - - val fixedVarInAlternative = - """|def g(pair: (Int,Int)): Int = pair match { - | case (1, n) => n - | case (n, 1) => n - | case _ => 0 - |}""".stripMargin - - em"""|Variables are not allowed within alternate pattern matches. You can workaround - |this issue by adding additional cases for each alternative. For example, the - |illegal function: - | - |$varInAlternative - |could be implemented by moving each alternative into a separate case: - | - |$fixedVarInAlternative""" - } - } +class RepeatedModifier(modifier: String)(implicit ctx:Context) +extends SyntaxMsg(RepeatedModifierID) { + def msg(using Context) = i"""Repeated modifier $modifier""" - class IdentifierExpected(identifier: String)(using Context) - extends SyntaxMsg(IdentifierExpectedID) { - def msg = "identifier expected" - def explain = { - val wrongIdentifier = em"def foo: $identifier = {...}" - val validIdentifier = em"def foo = {...}" - em"""|An identifier expected, but $identifier found. This could be because - |$identifier is not a valid identifier. As a workaround, the compiler could - |infer the type for you. For example, instead of: - | - |$wrongIdentifier - | - |Write your code like: - | - |$validIdentifier - | - |""" - } + def explain(using Context) = { + val code1 = "private private val Origin = Point(0, 0)" + val code2 = "private final val Origin = Point(0, 0)" + i"""This happens when you accidentally specify the same modifier twice. + | + |Example: + | + |$code1 + | + |instead of + | + |$code2 + | + |""" } +} - class AuxConstructorNeedsNonImplicitParameter()(implicit ctx:Context) - extends SyntaxMsg(AuxConstructorNeedsNonImplicitParameterID) { - def msg = "Auxiliary constructor needs non-implicit parameter list" - def explain = - em"""|Only the primary constructor is allowed an ${hl("implicit")} parameter list; - |auxiliary constructors need non-implicit parameter lists. When a primary - |constructor has an implicit argslist, auxiliary constructors that call the - |primary constructor must specify the implicit value. - | - |To resolve this issue check for: - | - Forgotten parenthesis on ${hl("this")} (${hl("def this() = { ... }")}) - | - Auxiliary constructors specify the implicit value - |""" +class InterpolatedStringError()(implicit ctx:Context) +extends SyntaxMsg(InterpolatedStringErrorID) { + def msg(using Context) = "Error in interpolated string: identifier or block expected" + def explain(using Context) = { + val code1 = "s\"$new Point(0, 0)\"" + val code2 = "s\"${new Point(0, 0)}\"" + i"""|This usually happens when you forget to place your expressions inside curly braces. + | + |$code1 + | + |should be written as + | + |$code2 + |""" } +} - class IllegalLiteral()(using Context) - extends SyntaxMsg(IllegalLiteralID) { - def msg = "Illegal literal" - def explain = - em"""|Available literals can be divided into several groups: - | - Integer literals: 0, 21, 0xFFFFFFFF, -42L - | - Floating Point Literals: 0.0, 1e30f, 3.14159f, 1.0e-100, .1 - | - Boolean Literals: true, false - | - Character Literals: 'a', '\u0041', '\n' - | - String Literals: "Hello, World!" - | - null - |""" - } +class UnboundPlaceholderParameter()(implicit ctx:Context) +extends SyntaxMsg(UnboundPlaceholderParameterID) { + def msg(using Context) = i"""Unbound placeholder parameter; incorrect use of ${hl("_")}""" + def explain(using Context) = + i"""|The ${hl("_")} placeholder syntax was used where it could not be bound. + |Consider explicitly writing the variable binding. + | + |This can be done by replacing ${hl("_")} with a variable (eg. ${hl("x")}) + |and adding ${hl("x =>")} where applicable. + | + |Example before: + | + |${hl("{ _ }")} + | + |Example after: + | + |${hl("x => { x }")} + | + |Another common occurrence for this error is defining a val with ${hl("_")}: + | + |${hl("val a = _")} + | + |But this val definition isn't very useful, it can never be assigned + |another value. And thus will always remain uninitialized. + |Consider replacing the ${hl("val")} with ${hl("var")}: + | + |${hl("var a = _")} + | + |Note that this use of ${hl("_")} is not placeholder syntax, + |but an uninitialized var definition. + |Only fields can be left uninitialized in this manner; local variables + |must be initialized. + | + |Another occurrence for this error is self type definition. + |The ${hl("_")} can be replaced with ${hl("this")}. + | + |Example before: + | + |${hl("trait A { _: B => ... ")} + | + |Example after: + | + |${hl("trait A { this: B => ... ")} + |""" +} - class LossyWideningConstantConversion(sourceType: Type, targetType: Type)(using Context) - extends Message(LossyWideningConstantConversionID): - def kind = MessageKind.LossyConversion - def msg = em"""|Widening conversion from $sourceType to $targetType loses precision. - |Write `.to$targetType` instead.""".stripMargin - def explain = "" - - class PatternMatchExhaustivity(uncoveredFn: -> String, hasMore: Boolean)(using Context) - extends Message(PatternMatchExhaustivityID) { - def kind = MessageKind.PatternMatchExhaustivity - lazy val uncovered = uncoveredFn - def msg = - val addendum = if hasMore then "(More unmatched cases are elided)" else "" - em"""|${hl("match")} may not be exhaustive. - | - |It would fail on pattern case: $uncovered - |$addendum""" - - - def explain = - em"""|There are several ways to make the match exhaustive: - | - Add missing cases as shown in the warning - | - If an extractor always return ${hl("Some(...)")}, write ${hl("Some[X]")} for its return type - | - Add a ${hl("case _ => ...")} at the end to match all remaining cases - |""" +class IllegalStartSimpleExpr(illegalToken: String)(using Context) +extends SyntaxMsg(IllegalStartSimpleExprID) { + def msg(using Context) = i"expression expected but ${Red(illegalToken)} found" + def explain(using Context) = { + i"""|An expression cannot start with ${Red(illegalToken)}.""" } +} - class UncheckedTypePattern(msgFn: -> String)(using Context) - extends PatternMatchMsg(UncheckedTypePatternID) { - def msg = msgFn - def explain = - em"""|Type arguments and type refinements are erased during compile time, thus it's - |impossible to check them at run-time. - | - |You can either replace the type arguments by ${hl("_")} or use `@unchecked`. - |""" - } +class MissingReturnType()(implicit ctx:Context) +extends SyntaxMsg(MissingReturnTypeID) { + def msg(using Context) = "Missing return type" + def explain(using Context) = + i"""|An abstract declaration must have a return type. For example: + | + |trait Shape: + | ${hl("def area: Double")} // abstract declaration returning a Double""" +} + +class MissingReturnTypeWithReturnStatement(method: Symbol)(using Context) +extends SyntaxMsg(MissingReturnTypeWithReturnStatementID) { + def msg(using Context) = i"$method has a return statement; it needs a result type" + def explain(using Context) = + i"""|If a method contains a ${hl("return")} statement, it must have an + |explicit return type. For example: + | + |${hl("def good: Int /* explicit return type */ = return 1")}""" +} - class MatchCaseUnreachable()(using Context) - extends Message(MatchCaseUnreachableID) { - def kind = MessageKind.MatchCaseUnreachable - def msg = "Unreachable case" - def explain = "" - } +class YieldOrDoExpectedInForComprehension()(using Context) +extends SyntaxMsg(YieldOrDoExpectedInForComprehensionID) { + def msg(using Context) = i"${hl("yield")} or ${hl("do")} expected" - class MatchCaseOnlyNullWarning()(using Context) - extends PatternMatchMsg(MatchCaseOnlyNullWarningID) { - def msg = em"""Unreachable case except for ${hl("null")} (if this is intentional, consider writing ${hl("case null =>")} instead).""" - def explain = "" - } + def explain(using Context) = + i"""|When the enumerators in a for comprehension are not placed in parentheses or + |braces, a ${hl("do")} or ${hl("yield")} statement is required after the enumerators + |section of the comprehension. + | + |You can save some keystrokes by omitting the parentheses and writing + | + |${hl("val numbers = for i <- 1 to 3 yield i")} + | + | instead of + | + |${hl("val numbers = for (i <- 1 to 3) yield i")} + | + |but the ${hl("yield")} keyword is still required. + | + |For comprehensions that simply perform a side effect without yielding anything + |can also be written without parentheses but a ${hl("do")} keyword has to be + |included. For example, + | + |${hl("for (i <- 1 to 3) println(i)")} + | + |can be written as + | + |${hl("for i <- 1 to 3 do println(i) // notice the 'do' keyword")} + | + |""" +} + +class ProperDefinitionNotFound()(using Context) +extends Message(ProperDefinitionNotFoundID) { + def kind = MessageKind.DocComment + def msg(using Context) = i"""Proper definition was not found in ${hl("@usecase")}""" + + def explain(using Context) = { + val noUsecase = + "def map[B, That](f: A => B)(implicit bf: CanBuildFrom[List[A], B, That]): That" + + val usecase = + """|/** Map from List[A] => List[B] + | * + | * @usecase def map[B](f: A => B): List[B] + | */ + |def map[B, That](f: A => B)(implicit bf: CanBuildFrom[List[A], B, That]): That + |""".stripMargin - class MatchableWarning(tp: Type, pattern: Boolean)(using Context) - extends TypeMsg(MatchableWarningID) { - def msg = - val kind = if pattern then "pattern selector" else "value" - em"""${kind} should be an instance of Matchable,, - |but it has unmatchable type $tp instead""" - - def explain = - if pattern then - em"""A value of type $tp cannot be the selector of a match expression - |since it is not constrained to be `Matchable`. Matching on unconstrained - |values is disallowed since it can uncover implementation details that - |were intended to be hidden and thereby can violate paramtetricity laws - |for reasoning about programs. - | - |The restriction can be overridden by appending `.asMatchable` to - |the selector value. `asMatchable` needs to be imported from - |scala.compiletime. Example: - | - | import compiletime.asMatchable - | def f[X](x: X) = x.asMatchable match { ... }""" - else - em"""The value can be converted to a `Matchable` by appending `.asMatchable`. - |`asMatchable` needs to be imported from scala.compiletime.""" + i"""|Usecases are only supported for ${hl("def")}s. They exist because with Scala's + |advanced type-system, we sometimes end up with seemingly scary signatures. + |The usage of these methods, however, needs not be - for instance the ${hl("map")} + |function + | + |${hl("List(1, 2, 3).map(2 * _) // res: List(2, 4, 6)")} + | + |is easy to understand and use - but has a rather bulky signature: + | + |$noUsecase + | + |to mitigate this and ease the usage of such functions we have the ${hl("@usecase")} + |annotation for docstrings. Which can be used like this: + | + |$usecase + | + |When creating the docs, the signature of the method is substituted by the + |usecase and the compiler makes sure that it is valid. Because of this, you're + |only allowed to use ${hl("def")}s when defining usecases.""" } +} - class SeqWildcardPatternPos()(using Context) - extends SyntaxMsg(SeqWildcardPatternPosID) { - def msg = em"""${hl("*")} can be used only for last argument""" - def explain = { - val code = - """def sumOfTheFirstTwo(list: List[Int]): Int = list match { - | case List(first, second, x*) => first + second - | case _ => 0 - |}""" - em"""|Sequence wildcard pattern is expected at the end of an argument list. - |This pattern matches any remaining elements in a sequence. - |Consider the following example: - | - |$code - | - |Calling: - | - |${hl("sumOfTheFirstTwo(List(1, 2, 10))")} - | - |would give 3 as a result""" - } - } +class ByNameParameterNotSupported(tpe: untpd.Tree)(using Context) +extends SyntaxMsg(ByNameParameterNotSupportedID) { + def msg(using Context) = i"By-name parameter type ${tpe} not allowed here." - class IllegalStartOfSimplePattern()(using Context) - extends SyntaxMsg(IllegalStartOfSimplePatternID) { - def msg = "pattern expected" - def explain = { - val sipCode = - """def f(x: Int, y: Int) = x match { - | case `y` => ... - |} - """ - val constructorPatternsCode = - """case class Person(name: String, age: Int) + def explain(using Context) = + i"""|By-name parameters act like functions that are only evaluated when referenced, + |allowing for lazy evaluation of a parameter. + | + |An example of using a by-name parameter would look like: + |${hl("def func(f: => Boolean) = f // 'f' is evaluated when referenced within the function")} + | + |An example of the syntax of passing an actual function as a parameter: + |${hl("def func(f: (Boolean => Boolean)) = f(true)")} + | + |or: + | + |${hl("def func(f: Boolean => Boolean) = f(true)")} + | + |And the usage could be as such: + |${hl("func(bool => // do something...)")} + |""" +} + +class WrongNumberOfTypeArgs(fntpe: Type, expectedArgs: List[ParamInfo], actual: List[untpd.Tree])(using Context) +extends SyntaxMsg(WrongNumberOfTypeArgsID) { + + private val expectedCount = expectedArgs.length + private val actualCount = actual.length + private val msgPrefix = if (actualCount > expectedCount) "Too many" else "Not enough" + + def msg(using Context) = + val expectedArgString = expectedArgs + .map(_.paramName.unexpandedName.show) + .mkString("[", ", ", "]") + val actualArgString = actual.map(_.show).mkString("[", ", ", "]") + val prettyName = + try fntpe.termSymbol match + case NoSymbol => fntpe.show + case symbol => symbol.showFullName + catch case NonFatal(ex) => fntpe.show + i"""|$msgPrefix type arguments for $prettyName$expectedArgString + |expected: $expectedArgString + |actual: $actualArgString""" + + def explain(using Context) = { + val tooManyTypeParams = + """|val tuple2: (Int, String) = (1, "one") + |val list: List[(Int, String)] = List(tuple2)""".stripMargin + + if (actualCount > expectedCount) + i"""|You have supplied too many type parameters | - |def test(p: Person) = p match { - | case Person(name, age) => ... - |} - """ - val tupplePatternsCode = - """def swap(tuple: (String, Int)): (Int, String) = tuple match { - | case (text, number) => (number, text) - |} - """ - val patternSequencesCode = - """def getSecondValue(list: List[Int]): Int = list match { - | case List(_, second, x:_*) => second + |For example List takes a single type parameter (List[A]) + |If you need to hold more types in a list then you need to combine them + |into another data type that can contain the number of types you need, + |In this example one solution would be to use a Tuple: + | + |${tooManyTypeParams}""" + else + i"""|You have not supplied enough type parameters + |If you specify one type parameter then you need to specify every type parameter.""" + } +} + +class IllegalVariableInPatternAlternative(name: Name)(using Context) +extends SyntaxMsg(IllegalVariableInPatternAlternativeID) { + def msg(using Context) = i"Illegal variable $name in pattern alternative" + def explain(using Context) = { + val varInAlternative = + """|def g(pair: (Int,Int)): Int = pair match { + | case (1, n) | (n, 1) => n | case _ => 0 - |}""" - em"""|Simple patterns can be divided into several groups: - |- Variable Patterns: ${hl("case x => ...")}. - | It matches any value, and binds the variable name to that value. - | A special case is the wild-card pattern _ which is treated as if it was a fresh - | variable on each occurrence. - | - |- Typed Patterns: ${hl("case x: Int => ...")} or ${hl("case _: Int => ...")}. - | This pattern matches any value matched by the specified type; it binds the variable - | name to that value. - | - |- Literal Patterns: ${hl("case 123 => ...")} or ${hl("case 'A' => ...")}. - | This type of pattern matches any value that is equal to the specified literal. - | - |- Stable Identifier Patterns: - | - | $sipCode - | - | the match succeeds only if the x argument and the y argument of f are equal. - | - |- Constructor Patterns: - | - | $constructorPatternsCode - | - | The pattern binds all object's fields to the variable names (name and age, in this - | case). - | - |- Tuple Patterns: - | - | $tupplePatternsCode - | - | Calling: - | - | ${hl("""swap(("Luftballons", 99)""")} - | - | would give ${hl("""(99, "Luftballons")""")} as a result. - | - |- Pattern Sequences: - | - | $patternSequencesCode - | - | Calling: - | - | ${hl("getSecondValue(List(1, 10, 2))")} - | - | would give 10 as a result. - | This pattern is possible because a companion object for the List class has a method - | with the following signature: - | - | ${hl("def unapplySeq[A](x: List[A]): Some[List[A]]")} - |""" - } - } - - class PkgDuplicateSymbol(existing: Symbol)(using Context) - extends NamingMsg(PkgDuplicateSymbolID) { - def msg = em"Trying to define package with same name as $existing" - def explain = "" - } + |}""".stripMargin - class ExistentialTypesNoLongerSupported()(using Context) - extends SyntaxMsg(ExistentialTypesNoLongerSupportedID) { - def msg = - em"""|Existential types are no longer supported - - |use a wildcard or dependent type instead""" - def explain = - em"""|The use of existential types is no longer supported. - | - |You should use a wildcard or dependent type instead. - | - |For example: - | - |Instead of using ${hl("forSome")} to specify a type variable - | - |${hl("List[T forSome { type T }]")} - | - |Try using a wildcard type variable - | - |${hl("List[?]")} - |""" - } + val fixedVarInAlternative = + """|def g(pair: (Int,Int)): Int = pair match { + | case (1, n) => n + | case (n, 1) => n + | case _ => 0 + |}""".stripMargin - class UnboundWildcardType()(using Context) - extends SyntaxMsg(UnboundWildcardTypeID) { - def msg = "Unbound wildcard type" - def explain = - em"""|The wildcard type syntax (${hl("_")}) was used where it could not be bound. - |Replace ${hl("_")} with a non-wildcard type. If the type doesn't matter, - |try replacing ${hl("_")} with ${hl("Any")}. - | - |Examples: - | - |- Parameter lists - | - | Instead of: - | ${hl("def foo(x: _) = ...")} - | - | Use ${hl("Any")} if the type doesn't matter: - | ${hl("def foo(x: Any) = ...")} - | - |- Type arguments - | - | Instead of: - | ${hl("val foo = List[?](1, 2)")} - | - | Use: - | ${hl("val foo = List[Int](1, 2)")} - | - |- Type bounds - | - | Instead of: - | ${hl("def foo[T <: _](x: T) = ...")} - | - | Remove the bounds if the type doesn't matter: - | ${hl("def foo[T](x: T) = ...")} - | - |- ${hl("val")} and ${hl("def")} types - | - | Instead of: - | ${hl("val foo: _ = 3")} - | - | Use: - | ${hl("val foo: Int = 3")} - |""" + i"""|Variables are not allowed within alternate pattern matches. You can workaround + |this issue by adding additional cases for each alternative. For example, the + |illegal function: + | + |$varInAlternative + |could be implemented by moving each alternative into a separate case: + | + |$fixedVarInAlternative""" + } +} + +class IdentifierExpected(identifier: String)(using Context) +extends SyntaxMsg(IdentifierExpectedID) { + def msg(using Context) = "identifier expected" + def explain(using Context) = { + val wrongIdentifier = i"def foo: $identifier = {...}" + val validIdentifier = i"def foo = {...}" + i"""|An identifier expected, but $identifier found. This could be because + |$identifier is not a valid identifier. As a workaround, the compiler could + |infer the type for you. For example, instead of: + | + |$wrongIdentifier + | + |Write your code like: + | + |$validIdentifier + | + |""" } +} - class OverridesNothing(member: Symbol)(using Context) - extends DeclarationMsg(OverridesNothingID) { - def msg = em"""${member} overrides nothing""" - - def explain = - em"""|There must be a field or method with the name ${member.name} in a super - |class of ${member.owner} to override it. Did you misspell it? - |Are you extending the right classes? - |""" - } +class AuxConstructorNeedsNonImplicitParameter()(implicit ctx:Context) +extends SyntaxMsg(AuxConstructorNeedsNonImplicitParameterID) { + def msg(using Context) = "Auxiliary constructor needs non-implicit parameter list" + def explain(using Context) = + i"""|Only the primary constructor is allowed an ${hl("implicit")} parameter list; + |auxiliary constructors need non-implicit parameter lists. When a primary + |constructor has an implicit argslist, auxiliary constructors that call the + |primary constructor must specify the implicit value. + | + |To resolve this issue check for: + | - Forgotten parenthesis on ${hl("this")} (${hl("def this() = { ... }")}) + | - Auxiliary constructors specify the implicit value + |""" +} + +class IllegalLiteral()(using Context) +extends SyntaxMsg(IllegalLiteralID) { + def msg(using Context) = "Illegal literal" + def explain(using Context) = + i"""|Available literals can be divided into several groups: + | - Integer literals: 0, 21, 0xFFFFFFFF, -42L + | - Floating Point Literals: 0.0, 1e30f, 3.14159f, 1.0e-100, .1 + | - Boolean Literals: true, false + | - Character Literals: 'a', '\u0041', '\n' + | - String Literals: "Hello, World!" + | - null + |""" +} + +class LossyWideningConstantConversion(sourceType: Type, targetType: Type)(using Context) +extends Message(LossyWideningConstantConversionID): + def kind = MessageKind.LossyConversion + def msg(using Context) = i"""|Widening conversion from $sourceType to $targetType loses precision. + |Write `.to$targetType` instead.""" + def explain(using Context) = "" + +class PatternMatchExhaustivity(uncoveredFn: -> String, hasMore: Boolean)(using Context) +extends Message(PatternMatchExhaustivityID) { + def kind = MessageKind.PatternMatchExhaustivity + lazy val uncovered = uncoveredFn + def msg(using Context) = + val addendum = if hasMore then "(More unmatched cases are elided)" else "" + i"""|${hl("match")} may not be exhaustive. + | + |It would fail on pattern case: $uncovered + |$addendum""" - class OverridesNothingButNameExists(member: Symbol, existing: List[Denotations.SingleDenotation])(using Context) - extends DeclarationMsg(OverridesNothingButNameExistsID) { - def msg = - val what = - if !existing.exists(_.symbol.hasTargetName(member.targetName)) - then "target name" - else "signature" - em"""${member} has a different $what than the overridden declaration""" - def explain = - val existingDecl: String = existing.map(_.showDcl).mkString(" \n") - em"""|There must be a non-final field or method with the name ${member.name} and the - |same parameter list in a super class of ${member.owner} to override it. - | - | ${member.showDcl} - | - |The super classes of ${member.owner} contain the following members - |named ${member.name}: - | ${existingDecl} - |""" - } - class OverrideError(override val msg: String) extends DeclarationMsg(OverrideErrorID): - def explain = "" - - class OverrideTypeMismatchError(override val msg: String, memberTp: Type, otherTp: Type)(using Context) - extends DeclarationMsg(OverrideTypeMismatchErrorID): - def explain = err.whyNoMatchStr(memberTp, otherTp) - override def canExplain = true - - class ForwardReferenceExtendsOverDefinition(value: Symbol, definition: Symbol)(using Context) - extends ReferenceMsg(ForwardReferenceExtendsOverDefinitionID) { - def msg = em"${definition.name} is a forward reference extending over the definition of ${value.name}" - - def explain = - em"""|${definition.name} is used before you define it, and the definition of ${value.name} - |appears between that use and the definition of ${definition.name}. - | - |Forward references are allowed only, if there are no value definitions between - |the reference and the referred method definition. - | - |Define ${definition.name} before it is used, - |or move the definition of ${value.name} so it does not appear between - |the declaration of ${definition.name} and its use, - |or define ${value.name} as lazy. - |""".stripMargin + def explain(using Context) = + i"""|There are several ways to make the match exhaustive: + | - Add missing cases as shown in the warning + | - If an extractor always return ${hl("Some(...)")}, write ${hl("Some[X]")} for its return type + | - Add a ${hl("case _ => ...")} at the end to match all remaining cases + |""" +} + +class UncheckedTypePattern(msgFn: -> String)(using Context) + extends PatternMatchMsg(UncheckedTypePatternID) { + def msg(using Context) = msgFn + def explain(using Context) = + i"""|Type arguments and type refinements are erased during compile time, thus it's + |impossible to check them at run-time. + | + |You can either replace the type arguments by ${hl("_")} or use `@unchecked`. + |""" +} + +class MatchCaseUnreachable()(using Context) +extends Message(MatchCaseUnreachableID) { + def kind = MessageKind.MatchCaseUnreachable + def msg(using Context) = "Unreachable case" + def explain(using Context) = "" +} + +class MatchCaseOnlyNullWarning()(using Context) +extends PatternMatchMsg(MatchCaseOnlyNullWarningID) { + def msg(using Context) = i"""Unreachable case except for ${hl("null")} (if this is intentional, consider writing ${hl("case null =>")} instead).""" + def explain(using Context) = "" +} + +class MatchableWarning(tp: Type, pattern: Boolean)(using Context) +extends TypeMsg(MatchableWarningID) { + def msg(using Context) = + val kind = if pattern then "pattern selector" else "value" + i"""${kind} should be an instance of Matchable,, + |but it has unmatchable type $tp instead""" + + def explain(using Context) = + if pattern then + i"""A value of type $tp cannot be the selector of a match expression + |since it is not constrained to be `Matchable`. Matching on unconstrained + |values is disallowed since it can uncover implementation details that + |were intended to be hidden and thereby can violate paramtetricity laws + |for reasoning about programs. + | + |The restriction can be overridden by appending `.asMatchable` to + |the selector value. `asMatchable` needs to be imported from + |scala.compiletime. Example: + | + | import compiletime.asMatchable + | def f[X](x: X) = x.asMatchable match { ... }""" + else + i"""The value can be converted to a `Matchable` by appending `.asMatchable`. + |`asMatchable` needs to be imported from scala.compiletime.""" +} + +class SeqWildcardPatternPos()(using Context) +extends SyntaxMsg(SeqWildcardPatternPosID) { + def msg(using Context) = i"""${hl("*")} can be used only for last argument""" + def explain(using Context) = { + val code = + """def sumOfTheFirstTwo(list: List[Int]): Int = list match { + | case List(first, second, x*) => first + second + | case _ => 0 + |}""" + i"""|Sequence wildcard pattern is expected at the end of an argument list. + |This pattern matches any remaining elements in a sequence. + |Consider the following example: + | + |$code + | + |Calling: + | + |${hl("sumOfTheFirstTwo(List(1, 2, 10))")} + | + |would give 3 as a result""" + } +} + +class IllegalStartOfSimplePattern()(using Context) +extends SyntaxMsg(IllegalStartOfSimplePatternID) { + def msg(using Context) = "pattern expected" + def explain(using Context) = { + val sipCode = + """def f(x: Int, y: Int) = x match { + | case `y` => ... + |} + """ + val constructorPatternsCode = + """case class Person(name: String, age: Int) + | + |def test(p: Person) = p match { + | case Person(name, age) => ... + |} + """ + val tupplePatternsCode = + """def swap(tuple: (String, Int)): (Int, String) = tuple match { + | case (text, number) => (number, text) + |} + """ + val patternSequencesCode = + """def getSecondValue(list: List[Int]): Int = list match { + | case List(_, second, x:_*) => second + | case _ => 0 + |}""" + i"""|Simple patterns can be divided into several groups: + |- Variable Patterns: ${hl("case x => ...")}. + | It matches any value, and binds the variable name to that value. + | A special case is the wild-card pattern _ which is treated as if it was a fresh + | variable on each occurrence. + | + |- Typed Patterns: ${hl("case x: Int => ...")} or ${hl("case _: Int => ...")}. + | This pattern matches any value matched by the specified type; it binds the variable + | name to that value. + | + |- Literal Patterns: ${hl("case 123 => ...")} or ${hl("case 'A' => ...")}. + | This type of pattern matches any value that is equal to the specified literal. + | + |- Stable Identifier Patterns: + | + | $sipCode + | + | the match succeeds only if the x argument and the y argument of f are equal. + | + |- Constructor Patterns: + | + | $constructorPatternsCode + | + | The pattern binds all object's fields to the variable names (name and age, in this + | case). + | + |- Tuple Patterns: + | + | $tupplePatternsCode + | + | Calling: + | + | ${hl("""swap(("Luftballons", 99)""")} + | + | would give ${hl("""(99, "Luftballons")""")} as a result. + | + |- Pattern Sequences: + | + | $patternSequencesCode + | + | Calling: + | + | ${hl("getSecondValue(List(1, 10, 2))")} + | + | would give 10 as a result. + | This pattern is possible because a companion object for the List class has a method + | with the following signature: + | + | ${hl("def unapplySeq[A](x: List[A]): Some[List[A]]")} + |""" } +} + +class PkgDuplicateSymbol(existing: Symbol)(using Context) +extends NamingMsg(PkgDuplicateSymbolID) { + def msg(using Context) = i"Trying to define package with same name as $existing" + def explain(using Context) = "" +} + +class ExistentialTypesNoLongerSupported()(using Context) +extends SyntaxMsg(ExistentialTypesNoLongerSupportedID) { + def msg(using Context) = + i"""|Existential types are no longer supported - + |use a wildcard or dependent type instead""" + def explain(using Context) = + i"""|The use of existential types is no longer supported. + | + |You should use a wildcard or dependent type instead. + | + |For example: + | + |Instead of using ${hl("forSome")} to specify a type variable + | + |${hl("List[T forSome { type T }]")} + | + |Try using a wildcard type variable + | + |${hl("List[?]")} + |""" +} + +class UnboundWildcardType()(using Context) +extends SyntaxMsg(UnboundWildcardTypeID) { + def msg(using Context) = "Unbound wildcard type" + def explain(using Context) = + i"""|The wildcard type syntax (${hl("_")}) was used where it could not be bound. + |Replace ${hl("_")} with a non-wildcard type. If the type doesn't matter, + |try replacing ${hl("_")} with ${hl("Any")}. + | + |Examples: + | + |- Parameter lists + | + | Instead of: + | ${hl("def foo(x: _) = ...")} + | + | Use ${hl("Any")} if the type doesn't matter: + | ${hl("def foo(x: Any) = ...")} + | + |- Type arguments + | + | Instead of: + | ${hl("val foo = List[?](1, 2)")} + | + | Use: + | ${hl("val foo = List[Int](1, 2)")} + | + |- Type bounds + | + | Instead of: + | ${hl("def foo[T <: _](x: T) = ...")} + | + | Remove the bounds if the type doesn't matter: + | ${hl("def foo[T](x: T) = ...")} + | + |- ${hl("val")} and ${hl("def")} types + | + | Instead of: + | ${hl("val foo: _ = 3")} + | + | Use: + | ${hl("val foo: Int = 3")} + |""" +} - class ExpectedTokenButFound(expected: Token, found: Token)(using Context) - extends SyntaxMsg(ExpectedTokenButFoundID) { +class OverridesNothing(member: Symbol)(using Context) +extends DeclarationMsg(OverridesNothingID) { + def msg(using Context) = i"""${member} overrides nothing""" - private lazy val foundText = Tokens.showToken(found) + def explain(using Context) = + i"""|There must be a field or method with the name ${member.name} in a super + |class of ${member.owner} to override it. Did you misspell it? + |Are you extending the right classes? + |""" +} + +class OverridesNothingButNameExists(member: Symbol, existing: List[Denotations.SingleDenotation])(using Context) +extends DeclarationMsg(OverridesNothingButNameExistsID) { + def msg(using Context) = + val what = + if !existing.exists(_.symbol.hasTargetName(member.targetName)) + then "target name" + else "signature" + i"""${member} has a different $what than the overridden declaration""" + def explain(using Context) = + val existingDecl: String = existing.map(_.showDcl).mkString(" \n") + i"""|There must be a non-final field or method with the name ${member.name} and the + |same parameter list in a super class of ${member.owner} to override it. + | + | ${member.showDcl} + | + |The super classes of ${member.owner} contain the following members + |named ${member.name}: + | ${existingDecl} + |""" +} + +class OverrideError( + core: Context ?-> String, base: Type, + member: Symbol, other: Symbol, + memberTp: Type, otherTp: Type)(using Context) +extends DeclarationMsg(OverrideErrorID), NoDisambiguation: + def msg(using Context) = + val isConcreteOverAbstract = + (other.owner isSubClass member.owner) && other.is(Deferred) && !member.is(Deferred) + def addendum = + if isConcreteOverAbstract then + i"""| + |(Note that ${err.infoStringWithLocation(other, base)} is abstract, + |and is therefore overridden by concrete ${err.infoStringWithLocation(member, base)})""" + else "" + i"""error overriding ${err.infoStringWithLocation(other, base)}; + | ${err.infoString(member, base, showLocation = member.owner != base.typeSymbol)} $core$addendum""" + override def canExplain = + memberTp.exists && otherTp.exists + def explain(using Context) = + if canExplain then err.whyNoMatchStr(memberTp, otherTp) else "" + +class ForwardReferenceExtendsOverDefinition(value: Symbol, definition: Symbol)(using Context) +extends ReferenceMsg(ForwardReferenceExtendsOverDefinitionID) { + def msg(using Context) = i"${definition.name} is a forward reference extending over the definition of ${value.name}" + + def explain(using Context) = + i"""|${definition.name} is used before you define it, and the definition of ${value.name} + |appears between that use and the definition of ${definition.name}. + | + |Forward references are allowed only, if there are no value definitions between + |the reference and the referred method definition. + | + |Define ${definition.name} before it is used, + |or move the definition of ${value.name} so it does not appear between + |the declaration of ${definition.name} and its use, + |or define ${value.name} as lazy. + |""" +} - def msg = - val expectedText = - if (Tokens.isIdentifier(expected)) "an identifier" - else Tokens.showToken(expected) - em"""${expectedText} expected, but ${foundText} found""" +class ExpectedTokenButFound(expected: Token, found: Token)(using Context) +extends SyntaxMsg(ExpectedTokenButFoundID) { - def explain = - if (Tokens.isIdentifier(expected) && Tokens.isKeyword(found)) - s""" - |If you want to use $foundText as identifier, you may put it in backticks: `${Tokens.tokenString(found)}`.""".stripMargin - else - "" - } + private def foundText = Tokens.showToken(found) - class MixedLeftAndRightAssociativeOps(op1: Name, op2: Name, op2LeftAssoc: Boolean)(using Context) - extends SyntaxMsg(MixedLeftAndRightAssociativeOpsID) { - def msg = - val op1Asso: String = if (op2LeftAssoc) "which is right-associative" else "which is left-associative" - val op2Asso: String = if (op2LeftAssoc) "which is left-associative" else "which is right-associative" - em"${op1} (${op1Asso}) and ${op2} ($op2Asso) have same precedence and may not be mixed" - def explain = - s"""|The operators ${op1} and ${op2} are used as infix operators in the same expression, - |but they bind to different sides: - |${op1} is applied to the operand to its ${if (op2LeftAssoc) "right" else "left"} - |${op2} is applied to the operand to its ${if (op2LeftAssoc) "left" else "right"} - |As both have the same precedence the compiler can't decide which to apply first. - | - |You may use parenthesis to make the application order explicit, - |or use method application syntax operand1.${op1}(operand2). - | - |Operators ending in a colon ${hl(":")} are right-associative. All other operators are left-associative. - | - |Infix operator precedence is determined by the operator's first character. Characters are listed - |below in increasing order of precedence, with characters on the same line having the same precedence. - | (all letters) - | | - | ^ - | & - | = ! - | < > - | : - | + - - | * / % - | (all other special characters) - |Operators starting with a letter have lowest precedence, followed by operators starting with `|`, etc. - |""".stripMargin - } + def msg(using Context) = + val expectedText = + if (Tokens.isIdentifier(expected)) "an identifier" + else Tokens.showToken(expected) + i"""${expectedText} expected, but ${foundText} found""" - class CantInstantiateAbstractClassOrTrait(cls: Symbol, isTrait: Boolean)(using Context) - extends TypeMsg(CantInstantiateAbstractClassOrTraitID) { - private val traitOrAbstract = if (isTrait) "a trait" else "abstract" - def msg = em"""${cls.name} is ${traitOrAbstract}; it cannot be instantiated""" - def explain = - em"""|Abstract classes and traits need to be extended by a concrete class or object - |to make their functionality accessible. - | - |You may want to create an anonymous class extending ${cls.name} with - | ${s"class ${cls.name} { }"} - | - |or add a companion object with - | ${s"object ${cls.name} extends ${cls.name}"} - | - |You need to implement any abstract members in both cases. - |""".stripMargin - } - - class UnreducibleApplication(tycon: Type)(using Context) extends TypeMsg(UnreducibleApplicationID): - def msg = em"unreducible application of higher-kinded type $tycon to wildcard arguments" - def explain = - em"""|An abstract type constructor cannot be applied to wildcard arguments. - |Such applications are equivalent to existential types, which are not - |supported in Scala 3.""" - - class OverloadedOrRecursiveMethodNeedsResultType(cycleSym: Symbol)(using Context) - extends CyclicMsg(OverloadedOrRecursiveMethodNeedsResultTypeID) { - def msg = em"""Overloaded or recursive $cycleSym needs return type""" - def explain = - em"""Case 1: $cycleSym is overloaded - |If there are multiple methods named $cycleSym and at least one definition of - |it calls another, you need to specify the calling method's return type. - | - |Case 2: $cycleSym is recursive - |If $cycleSym calls itself on any path (even through mutual recursion), you need to specify the return type - |of $cycleSym or of a definition it's mutually recursive with. - |""".stripMargin - } - - class RecursiveValueNeedsResultType(cycleSym: Symbol)(using Context) - extends CyclicMsg(RecursiveValueNeedsResultTypeID) { - def msg = em"""Recursive $cycleSym needs type""" - def explain = - em"""The definition of $cycleSym is recursive and you need to specify its type. - |""".stripMargin - } - - class CyclicReferenceInvolving(denot: SymDenotation)(using Context) - extends CyclicMsg(CyclicReferenceInvolvingID) { - def msg = - val where = if denot.exists then s" involving $denot" else "" - em"Cyclic reference$where" - def explain = - em"""|$denot is declared as part of a cycle which makes it impossible for the - |compiler to decide upon ${denot.name}'s type. - |To avoid this error, try giving ${denot.name} an explicit type. - |""".stripMargin - } + def explain(using Context) = + if (Tokens.isIdentifier(expected) && Tokens.isKeyword(found)) + s""" + |If you want to use $foundText as identifier, you may put it in backticks: `${Tokens.tokenString(found)}`.""".stripMargin + else + "" +} + +class MixedLeftAndRightAssociativeOps(op1: Name, op2: Name, op2LeftAssoc: Boolean)(using Context) +extends SyntaxMsg(MixedLeftAndRightAssociativeOpsID) { + def msg(using Context) = + val op1Asso: String = if (op2LeftAssoc) "which is right-associative" else "which is left-associative" + val op2Asso: String = if (op2LeftAssoc) "which is left-associative" else "which is right-associative" + i"${op1} (${op1Asso}) and ${op2} ($op2Asso) have same precedence and may not be mixed" + def explain(using Context) = + s"""|The operators ${op1} and ${op2} are used as infix operators in the same expression, + |but they bind to different sides: + |${op1} is applied to the operand to its ${if (op2LeftAssoc) "right" else "left"} + |${op2} is applied to the operand to its ${if (op2LeftAssoc) "left" else "right"} + |As both have the same precedence the compiler can't decide which to apply first. + | + |You may use parenthesis to make the application order explicit, + |or use method application syntax operand1.${op1}(operand2). + | + |Operators ending in a colon ${hl(":")} are right-associative. All other operators are left-associative. + | + |Infix operator precedence is determined by the operator's first character. Characters are listed + |below in increasing order of precedence, with characters on the same line having the same precedence. + | (all letters) + | | + | ^ + | & + | = ! + | < > + | : + | + - + | * / % + | (all other special characters) + |Operators starting with a letter have lowest precedence, followed by operators starting with `|`, etc. + |""".stripMargin +} + +class CantInstantiateAbstractClassOrTrait(cls: Symbol, isTrait: Boolean)(using Context) +extends TypeMsg(CantInstantiateAbstractClassOrTraitID) { + private val traitOrAbstract = if (isTrait) "a trait" else "abstract" + def msg(using Context) = i"""${cls.name} is ${traitOrAbstract}; it cannot be instantiated""" + def explain(using Context) = + i"""|Abstract classes and traits need to be extended by a concrete class or object + |to make their functionality accessible. + | + |You may want to create an anonymous class extending ${cls.name} with + | ${s"class ${cls.name} { }"} + | + |or add a companion object with + | ${s"object ${cls.name} extends ${cls.name}"} + | + |You need to implement any abstract members in both cases. + |""" +} + +class UnreducibleApplication(tycon: Type)(using Context) extends TypeMsg(UnreducibleApplicationID): + def msg(using Context) = i"unreducible application of higher-kinded type $tycon to wildcard arguments" + def explain(using Context) = + i"""|An abstract type constructor cannot be applied to wildcard arguments. + |Such applications are equivalent to existential types, which are not + |supported in Scala 3.""" + +class OverloadedOrRecursiveMethodNeedsResultType(cycleSym: Symbol)(using Context) +extends CyclicMsg(OverloadedOrRecursiveMethodNeedsResultTypeID) { + def msg(using Context) = i"""Overloaded or recursive $cycleSym needs return type""" + def explain(using Context) = + i"""Case 1: $cycleSym is overloaded + |If there are multiple methods named $cycleSym and at least one definition of + |it calls another, you need to specify the calling method's return type. + | + |Case 2: $cycleSym is recursive + |If $cycleSym calls itself on any path (even through mutual recursion), you need to specify the return type + |of $cycleSym or of a definition it's mutually recursive with. + |""" +} - class CyclicReferenceInvolvingImplicit(cycleSym: Symbol)(using Context) - extends CyclicMsg(CyclicReferenceInvolvingImplicitID) { - def msg = em"""Cyclic reference involving implicit $cycleSym""" - def explain = - em"""|$cycleSym is declared as part of a cycle which makes it impossible for the - |compiler to decide upon ${cycleSym.name}'s type. - |This might happen when the right hand-side of $cycleSym's definition involves an implicit search. - |To avoid this error, try giving ${cycleSym.name} an explicit type. - |""".stripMargin - } +class RecursiveValueNeedsResultType(cycleSym: Symbol)(using Context) +extends CyclicMsg(RecursiveValueNeedsResultTypeID) { + def msg(using Context) = i"""Recursive $cycleSym needs type""" + def explain(using Context) = + i"""The definition of $cycleSym is recursive and you need to specify its type. + |""" +} + +class CyclicReferenceInvolving(denot: SymDenotation)(using Context) +extends CyclicMsg(CyclicReferenceInvolvingID) { + def msg(using Context) = + val where = if denot.exists then s" involving $denot" else "" + i"Cyclic reference$where" + def explain(using Context) = + i"""|$denot is declared as part of a cycle which makes it impossible for the + |compiler to decide upon ${denot.name}'s type. + |To avoid this error, try giving ${denot.name} an explicit type. + |""" +} + +class CyclicReferenceInvolvingImplicit(cycleSym: Symbol)(using Context) +extends CyclicMsg(CyclicReferenceInvolvingImplicitID) { + def msg(using Context) = i"""Cyclic reference involving implicit $cycleSym""" + def explain(using Context) = + i"""|$cycleSym is declared as part of a cycle which makes it impossible for the + |compiler to decide upon ${cycleSym.name}'s type. + |This might happen when the right hand-side of $cycleSym's definition involves an implicit search. + |To avoid this error, try giving ${cycleSym.name} an explicit type. + |""" +} - class SkolemInInferred(tree: tpd.Tree, pt: Type, argument: tpd.Tree)(using Context) - extends TypeMsg(SkolemInInferredID): - private def argStr = +class SkolemInInferred(tree: tpd.Tree, pt: Type, argument: tpd.Tree)(using Context) +extends TypeMsg(SkolemInInferredID): + def msg(using Context) = + def argStr = if argument.isEmpty then "" else i" from argument of type ${argument.tpe.widen}" - def msg = - em"""Failure to generate given instance for type $pt$argStr) - | - |I found: $tree - |But the part corresponding to `` is not a reference that can be generated. - |This might be because resolution yielded as given instance a function that is not - |known to be total and side-effect free.""" - def explain = - em"""The part of given resolution that corresponds to `` produced a term that - |is not a stable reference. Therefore a given instance could not be generated. - | - |To trouble-shoot the problem, try to supply an explicit expression instead of - |relying on implicit search at this point.""" - - class SuperQualMustBeParent(qual: untpd.Ident, cls: ClassSymbol)(using Context) - extends ReferenceMsg(SuperQualMustBeParentID) { - def msg = em"""|$qual does not name a parent of $cls""" - def explain = - val parents: Seq[String] = (cls.info.parents map (_.typeSymbol.name.show)).sorted - em"""|When a qualifier ${hl("T")} is used in a ${hl("super")} prefix of the form ${hl("C.super[T]")}, - |${hl("T")} must be a parent type of ${hl("C")}. - | - |In this case, the parents of $cls are: - |${parents.mkString(" - ", "\n - ", "")} - |""".stripMargin - } - - class VarArgsParamMustComeLast()(using Context) - extends SyntaxMsg(VarArgsParamMustComeLastID) { - def msg = em"""${hl("varargs")} parameter must come last""" - def explain = - em"""|The ${hl("varargs")} field must be the last field in the method signature. - |Attempting to define a field in a method signature after a ${hl("varargs")} field is an error. - |""" - } - - import typer.Typer.BindingPrec - - class AmbiguousReference(name: Name, newPrec: BindingPrec, prevPrec: BindingPrec, prevCtx: Context)(using Context) - extends ReferenceMsg(AmbiguousReferenceID) { - - /** A string which explains how something was bound; Depending on `prec` this is either - * imported by - * or defined in - */ - private def bindingString(prec: BindingPrec, whereFound: Context, qualifier: String = "") = { - val howVisible = prec match { - case BindingPrec.Definition => "defined" - case BindingPrec.Inheritance => "inherited" - case BindingPrec.NamedImport => "imported by name" - case BindingPrec.WildImport => "imported" - case BindingPrec.PackageClause => "found" - case BindingPrec.NothingBound => assert(false) - } - if (prec.isImportPrec) { - ex"""$howVisible$qualifier by ${em"${whereFound.importInfo}"}""" - } else - ex"""$howVisible$qualifier in ${em"${whereFound.owner}"}""" - } - - def msg = - i"""|Reference to ${em"$name"} is ambiguous, - |it is both ${bindingString(newPrec, ctx)} - |and ${bindingString(prevPrec, prevCtx, " subsequently")}""" - - def explain = - em"""|The compiler can't decide which of the possible choices you - |are referencing with $name: A definition of lower precedence - |in an inner scope, or a definition with higher precedence in - |an outer scope. - |Note: - | - Definitions in an enclosing scope take precedence over inherited definitions - | - Definitions take precedence over imports - | - Named imports take precedence over wildcard imports - | - You may replace a name when imported using - | ${hl("import")} scala.{ $name => ${name.show + "Tick"} } - |""" - } - - class MethodDoesNotTakeParameters(tree: tpd.Tree)(using Context) - extends TypeMsg(MethodDoesNotTakeParametersId) { - def methodSymbol: Symbol = - def recur(t: tpd.Tree): Symbol = - val sym = tpd.methPart(t).symbol - if sym == defn.Any_typeCast then - t match - case TypeApply(Select(qual, _), _) => recur(qual) - case _ => sym - else sym - recur(tree) - - def msg = { - val more = if (tree.isInstanceOf[tpd.Apply]) " more" else "" - val meth = methodSymbol - val methStr = if (meth.exists) meth.showLocated else "expression" - em"$methStr does not take$more parameters" - } - - def explain = { - val isNullary = methodSymbol.info.isInstanceOf[ExprType] - val addendum = - if (isNullary) "\nNullary methods may not be called with parenthesis" - else "" - - "You have specified more parameter lists than defined in the method definition(s)." + addendum + i"""Failure to generate given instance for type $pt$argStr) + | + |I found: $tree + |But the part corresponding to `` is not a reference that can be generated. + |This might be because resolution yielded as given instance a function that is not + |known to be total and side-effect free.""" + def explain(using Context) = + i"""The part of given resolution that corresponds to `` produced a term that + |is not a stable reference. Therefore a given instance could not be generated. + | + |To trouble-shoot the problem, try to supply an explicit expression instead of + |relying on implicit search at this point.""" + +class SuperQualMustBeParent(qual: untpd.Ident, cls: ClassSymbol)(using Context) +extends ReferenceMsg(SuperQualMustBeParentID) { + def msg(using Context) = i"""|$qual does not name a parent of $cls""" + def explain(using Context) = + val parents: Seq[String] = (cls.info.parents map (_.typeSymbol.name.show)).sorted + i"""|When a qualifier ${hl("T")} is used in a ${hl("super")} prefix of the form ${hl("C.super[T]")}, + |${hl("T")} must be a parent type of ${hl("C")}. + | + |In this case, the parents of $cls are: + |${parents.mkString(" - ", "\n - ", "")} + |""" +} + +class VarArgsParamMustComeLast()(using Context) +extends SyntaxMsg(VarArgsParamMustComeLastID) { + def msg(using Context) = i"""${hl("varargs")} parameter must come last""" + def explain(using Context) = + i"""|The ${hl("varargs")} field must be the last field in the method signature. + |Attempting to define a field in a method signature after a ${hl("varargs")} field is an error. + |""" +} + +import typer.Typer.BindingPrec + +class AmbiguousReference(name: Name, newPrec: BindingPrec, prevPrec: BindingPrec, prevCtx: Context)(using Context) + extends ReferenceMsg(AmbiguousReferenceID), NoDisambiguation { + + /** A string which explains how something was bound; Depending on `prec` this is either + * imported by + * or defined in + */ + private def bindingString(prec: BindingPrec, whereFound: Context, qualifier: String = "")(using Context) = { + val howVisible = prec match { + case BindingPrec.Definition => "defined" + case BindingPrec.Inheritance => "inherited" + case BindingPrec.NamedImport => "imported by name" + case BindingPrec.WildImport => "imported" + case BindingPrec.PackageClause => "found" + case BindingPrec.NothingBound => assert(false) } - - } - - class AmbiguousOverload(tree: tpd.Tree, val alternatives: List[SingleDenotation], pt: Type, addendum: String = "")( - implicit ctx: Context) - extends ReferenceMsg(AmbiguousOverloadID) { - private def all = if (alternatives.length == 2) "both" else "all" - def msg = - em"""|Ambiguous overload. The ${err.overloadedAltsStr(alternatives)} - |$all match ${err.expectedTypeStr(pt)}$addendum""".stripMargin - def explain = - em"""|There are ${alternatives.length} methods that could be referenced as the compiler knows too little - |about the expected type. - |You may specify the expected type e.g. by - |- assigning it to a value with a specified type, or - |- adding a type ascription as in ${hl("instance.myMethod: String => Int")} - |""" - } - - class ReassignmentToVal(name: Name)(using Context) - extends TypeMsg(ReassignmentToValID) { - def msg = em"""Reassignment to val $name""" - def explain = - em"""|You can not assign a new value to $name as values can't be changed. - |Keep in mind that every statement has a value, so you may e.g. use - | ${hl("val")} $name ${hl("= if (condition) 2 else 5")} - |In case you need a reassignable name, you can declare it as - |variable - | ${hl("var")} $name ${hl("=")} ... - |""".stripMargin - } - - class TypeDoesNotTakeParameters(tpe: Type, params: List[untpd.Tree])(using Context) - extends TypeMsg(TypeDoesNotTakeParametersID) { - private def fboundsAddendum = - if tpe.typeSymbol.isAllOf(Provisional | TypeParam) then - "\n(Note that F-bounds of type parameters may not be type lambdas)" - else "" - def msg = em"$tpe does not take type parameters$fboundsAddendum" - def explain = - val ps = - if (params.size == 1) s"a type parameter ${params.head}" - else s"type parameters ${params.map(_.show).mkString(", ")}" - i"""You specified ${NoColor(ps)} for ${em"$tpe"}, which is not - |declared to take any. - |""" - } - - class VarValParametersMayNotBeCallByName(name: TermName, mutable: Boolean)(using Context) - extends SyntaxMsg(VarValParametersMayNotBeCallByNameID) { - def varOrVal = if (mutable) em"${hl("var")}" else em"${hl("val")}" - def msg = s"$varOrVal parameters may not be call-by-name" - def explain = - em"""${hl("var")} and ${hl("val")} parameters of classes and traits may no be call-by-name. In case you - |want the parameter to be evaluated on demand, consider making it just a parameter - |and a ${hl("def")} in the class such as - | ${s"class MyClass(${name}Tick: => String) {"} - | ${s" def $name() = ${name}Tick"} - | ${hl("}")} - |""" - } - - class MissingTypeParameterFor(tpe: Type)(using Context) - extends SyntaxMsg(MissingTypeParameterForID) { - def msg = - if (tpe.derivesFrom(defn.AnyKindClass)) em"${tpe} cannot be used as a value type" - else em"Missing type parameter for ${tpe}" - def explain = "" - } - - class MissingTypeParameterInTypeApp(tpe: Type)(using Context) - extends TypeMsg(MissingTypeParameterInTypeAppID) { - def numParams = tpe.typeParams.length - def parameters = if (numParams == 1) "parameter" else "parameters" - def msg = em"Missing type $parameters for $tpe" - def explain = em"A fully applied type is expected but $tpe takes $numParams $parameters" - } - - class MissingArgument(pname: Name, methString: String)(using Context) - extends TypeMsg(MissingArgumentID): - def msg = - if pname.firstPart contains '$' then s"not enough arguments for $methString" - else s"missing argument for parameter $pname of $methString" - def explain = "" - - class DoesNotConformToBound(tpe: Type, which: String, bound: Type)(using Context) - extends TypeMismatchMsg( - if which == "lower" then bound else tpe, - if which == "lower" then tpe else bound)(DoesNotConformToBoundID): - private def isBounds = tpe match - case TypeBounds(lo, hi) => lo ne hi - case _ => false - override def canExplain = !isBounds - def msg = - if isBounds then - em"Type argument ${tpe} does not overlap with $which bound $bound" - else - em"Type argument ${tpe} does not conform to $which bound $bound" - - class DoesNotConformToSelfType(category: String, selfType: Type, cls: Symbol, - otherSelf: Type, relation: String, other: Symbol)( - implicit ctx: Context) - extends TypeMismatchMsg(selfType, otherSelf)(DoesNotConformToSelfTypeID) { - def msg = em"""$category: self type $selfType of $cls does not conform to self type $otherSelf - |of $relation $other""" - } - - class DoesNotConformToSelfTypeCantBeInstantiated(tp: Type, selfType: Type)( - implicit ctx: Context) - extends TypeMismatchMsg(tp, selfType)(DoesNotConformToSelfTypeCantBeInstantiatedID) { - def msg = em"""$tp does not conform to its self type $selfType; cannot be instantiated""" - } - - class IllegalParameterInit(found: Type, expected: Type, param: Symbol, cls: Symbol)(using Context) - extends TypeMismatchMsg(found, expected)(IllegalParameterInitID): - def msg = - em"""illegal parameter initialization of $param. - | - | The argument passed for $param has type: $found - | but $cls expects $param to have type: $expected""" - - class AbstractMemberMayNotHaveModifier(sym: Symbol, flag: FlagSet)( - implicit ctx: Context) - extends SyntaxMsg(AbstractMemberMayNotHaveModifierID) { - def msg = em"""${hl("abstract")} $sym may not have `${flag.flagsString}` modifier""" - def explain = "" - } - - class TypesAndTraitsCantBeImplicit()(using Context) - extends SyntaxMsg(TypesAndTraitsCantBeImplicitID) { - def msg = em"""${hl("implicit")} modifier cannot be used for types or traits""" - def explain = "" - } - - class OnlyClassesCanBeAbstract(sym: Symbol)( - implicit ctx: Context) - extends SyntaxMsg(OnlyClassesCanBeAbstractID) { - def explain = "" - def msg = em"""${hl("abstract")} modifier can be used only for classes; it should be omitted for abstract members""" - } - - class AbstractOverrideOnlyInTraits(sym: Symbol)( - implicit ctx: Context) - extends SyntaxMsg(AbstractOverrideOnlyInTraitsID) { - def msg = em"""${hl("abstract override")} modifier only allowed for members of traits""" - def explain = "" - } - - class TraitsMayNotBeFinal(sym: Symbol)( - implicit ctx: Context) - extends SyntaxMsg(TraitsMayNotBeFinalID) { - def msg = em"""$sym may not be ${hl("final")}""" - def explain = - "A trait can never be final since it is abstract and must be extended to be useful." - } - - class NativeMembersMayNotHaveImplementation(sym: Symbol)( - implicit ctx: Context) - extends SyntaxMsg(NativeMembersMayNotHaveImplementationID) { - def msg = em"""${hl("@native")} members may not have an implementation""" - def explain = "" - } - - class TraitMayNotDefineNativeMethod(sym: Symbol)( - implicit ctx: Context) - extends SyntaxMsg(TraitMayNotDefineNativeMethodID) { - def msg = em"""A trait cannot define a ${hl("@native")} method.""" - def explain = "" - } - - class OnlyClassesCanHaveDeclaredButUndefinedMembers(sym: Symbol)( - implicit ctx: Context) - extends SyntaxMsg(OnlyClassesCanHaveDeclaredButUndefinedMembersID) { - - private def varNote = - if (sym.is(Mutable)) "Note that variables need to be initialized to be defined." + if (prec.isImportPrec) { + i"""$howVisible$qualifier by ${whereFound.importInfo}""" + } else + i"""$howVisible$qualifier in ${whereFound.owner}""" + } + + def msg(using Context) = + i"""|Reference to $name is ambiguous, + |it is both ${bindingString(newPrec, ctx)} + |and ${bindingString(prevPrec, prevCtx, " subsequently")}""" + + def explain(using Context) = + i"""|The compiler can't decide which of the possible choices you + |are referencing with $name: A definition of lower precedence + |in an inner scope, or a definition with higher precedence in + |an outer scope. + |Note: + | - Definitions in an enclosing scope take precedence over inherited definitions + | - Definitions take precedence over imports + | - Named imports take precedence over wildcard imports + | - You may replace a name when imported using + | ${hl("import")} scala.{ $name => ${name.show + "Tick"} } + |""" +} + +class MethodDoesNotTakeParameters(tree: tpd.Tree)(using Context) +extends TypeMsg(MethodDoesNotTakeParametersId) { + def methodSymbol(using Context): Symbol = + def recur(t: tpd.Tree): Symbol = + val sym = tpd.methPart(t).symbol + if sym == defn.Any_typeCast then + t match + case TypeApply(Select(qual, _), _) => recur(qual) + case _ => sym + else sym + recur(tree) + + def msg(using Context) = { + val more = if (tree.isInstanceOf[tpd.Apply]) " more" else "" + val meth = methodSymbol + val methStr = if (meth.exists) meth.showLocated else "expression" + i"$methStr does not take$more parameters" + } + + def explain(using Context) = { + val isNullary = methodSymbol.info.isInstanceOf[ExprType] + val addendum = + if (isNullary) "\nNullary methods may not be called with parenthesis" else "" - def msg = em"""Declaration of $sym not allowed here: only classes can have declared but undefined members""" - def explain = s"$varNote" - } - class CannotExtendAnyVal(sym: Symbol)(using Context) - extends SyntaxMsg(CannotExtendAnyValID) { - def msg = em"""$sym cannot extend ${hl("AnyVal")}""" - def explain = - em"""Only classes (not traits) are allowed to extend ${hl("AnyVal")}, but traits may extend - |${hl("Any")} to become ${Green("\"universal traits\"")} which may only have ${hl("def")} members. - |Universal traits can be mixed into classes that extend ${hl("AnyVal")}. - |""" + "You have specified more parameter lists than defined in the method definition(s)." + addendum } - class CannotExtendJavaEnum(sym: Symbol)(using Context) - extends SyntaxMsg(CannotExtendJavaEnumID) { - def msg = em"""$sym cannot extend ${hl("java.lang.Enum")}: only enums defined with the ${hl("enum")} syntax can""" - def explain = "" - } +} - class CannotExtendContextFunction(sym: Symbol)(using Context) - extends SyntaxMsg(CannotExtendFunctionID) { - def msg = em"""$sym cannot extend a context function class""" - def explain = "" - } - - class JavaEnumParentArgs(parent: Type)(using Context) - extends TypeMsg(JavaEnumParentArgsID) { - def msg = em"""not enough arguments for constructor Enum: ${hl("(name: String, ordinal: Int)")}: ${hl(parent.show)}""" - def explain = "" - } - - class CannotHaveSameNameAs(sym: Symbol, cls: Symbol, reason: CannotHaveSameNameAs.Reason)(using Context) - extends SyntaxMsg(CannotHaveSameNameAsID) { - import CannotHaveSameNameAs._ - def reasonMessage: String = reason match { - case CannotBeOverridden => "class definitions cannot be overridden" - case DefinedInSelf(self) => - s"""cannot define ${sym.showKind} member with the same name as a ${cls.showKind} member in self reference ${self.name}. - |(Note: this can be resolved by using another name) - |""".stripMargin - } - - def msg = em"""$sym cannot have the same name as ${cls.showLocated} -- """ + reasonMessage - def explain = "" - } - object CannotHaveSameNameAs { - sealed trait Reason - case object CannotBeOverridden extends Reason - case class DefinedInSelf(self: tpd.ValDef) extends Reason - } - - class ValueClassesMayNotDefineInner(valueClass: Symbol, inner: Symbol)(using Context) - extends SyntaxMsg(ValueClassesMayNotDefineInnerID) { - def msg = em"""Value classes may not define an inner class""" - def explain = "" - } - - class ValueClassesMayNotDefineNonParameterField(valueClass: Symbol, field: Symbol)(using Context) - extends SyntaxMsg(ValueClassesMayNotDefineNonParameterFieldID) { - def msg = em"""Value classes may not define non-parameter field""" - def explain = "" - } - - class ValueClassesMayNotDefineASecondaryConstructor(valueClass: Symbol, constructor: Symbol)(using Context) - extends SyntaxMsg(ValueClassesMayNotDefineASecondaryConstructorID) { - def msg = em"""Value classes may not define a secondary constructor""" - def explain = "" - } - - class ValueClassesMayNotContainInitalization(valueClass: Symbol)(using Context) - extends SyntaxMsg(ValueClassesMayNotContainInitalizationID) { - def msg = em"""Value classes may not contain initialization statements""" - def explain = "" - } - - class ValueClassesMayNotBeAbstract(valueClass: Symbol)(using Context) - extends SyntaxMsg(ValueClassesMayNotBeAbstractID) { - def msg = em"""Value classes may not be ${hl("abstract")}""" - def explain = "" - } - - class ValueClassesMayNotBeContainted(valueClass: Symbol)(using Context) - extends SyntaxMsg(ValueClassesMayNotBeContaintedID) { - private def localOrMember = if (valueClass.owner.isTerm) "local class" else "member of another class" - def msg = s"""Value classes may not be a $localOrMember""" - def explain = "" - } - - class ValueClassesMayNotWrapAnotherValueClass(valueClass: Symbol)(using Context) - extends SyntaxMsg(ValueClassesMayNotWrapAnotherValueClassID) { - def msg = """A value class may not wrap another user-defined value class""" - def explain = "" - } - - class ValueClassParameterMayNotBeAVar(valueClass: Symbol, param: Symbol)(using Context) - extends SyntaxMsg(ValueClassParameterMayNotBeAVarID) { - def msg = em"""A value class parameter may not be a ${hl("var")}""" - def explain = - em"""A value class must have exactly one ${hl("val")} parameter.""" - } - - class ValueClassNeedsOneValParam(valueClass: Symbol)(using Context) - extends SyntaxMsg(ValueClassNeedsExactlyOneValParamID) { - def msg = em"""Value class needs one ${hl("val")} parameter""" - def explain = "" - } - - class ValueClassParameterMayNotBeCallByName(valueClass: Symbol, param: Symbol)(using Context) - extends SyntaxMsg(ValueClassParameterMayNotBeCallByNameID) { - def msg = s"Value class parameter `${param.name}` may not be call-by-name" - def explain = "" - } - - class SuperCallsNotAllowedInlineable(symbol: Symbol)(using Context) - extends SyntaxMsg(SuperCallsNotAllowedInlineableID) { - def msg = em"Super call not allowed in inlineable $symbol" - def explain = "Method inlining prohibits calling superclass methods, as it may lead to confusion about which super is being called." - } - - class NotAPath(tp: Type, usage: String)(using Context) extends TypeMsg(NotAPathID): - def msg = em"$tp is not a valid $usage, since it is not an immutable path" - def explain = - i"""An immutable path is - | - a reference to an immutable value, or - | - a reference to `this`, or - | - a selection of an immutable path with an immutable value.""" - - class WrongNumberOfParameters(expected: Int)(using Context) - extends SyntaxMsg(WrongNumberOfParametersID) { - def msg = s"Wrong number of parameters, expected: $expected" - def explain = "" - } - - class DuplicatePrivateProtectedQualifier()(using Context) - extends SyntaxMsg(DuplicatePrivateProtectedQualifierID) { - def msg = "Duplicate private/protected qualifier" - def explain = - em"It is not allowed to combine `private` and `protected` modifiers even if they are qualified to different scopes" - } - - class ExpectedStartOfTopLevelDefinition()(using Context) - extends SyntaxMsg(ExpectedStartOfTopLevelDefinitionID) { - def msg = "Expected start of definition" - def explain = - em"You have to provide either ${hl("class")}, ${hl("trait")}, ${hl("object")}, or ${hl("enum")} definitions after qualifiers" - } - - class NoReturnFromInlineable(owner: Symbol)(using Context) - extends SyntaxMsg(NoReturnFromInlineableID) { - def msg = em"No explicit ${hl("return")} allowed from inlineable $owner" - def explain = - em"""Methods marked with ${hl("inline")} modifier may not use ${hl("return")} statements. - |Instead, you should rely on the last expression's value being - |returned from a method. - |""" - } - - class ReturnOutsideMethodDefinition(owner: Symbol)(using Context) - extends SyntaxMsg(ReturnOutsideMethodDefinitionID) { - def msg = em"${hl("return")} outside method definition" - def explain = - em"""You used ${hl("return")} in ${owner}. - |${hl("return")} is a keyword and may only be used within method declarations. - |""" - } - - class ExtendFinalClass(clazz:Symbol, finalClazz: Symbol)(using Context) - extends SyntaxMsg(ExtendFinalClassID) { - def msg = em"$clazz cannot extend ${hl("final")} $finalClazz" - def explain = - em"""A class marked with the ${hl("final")} keyword cannot be extended""" - } - - class ExpectedTypeBoundOrEquals(found: Token)(using Context) - extends SyntaxMsg(ExpectedTypeBoundOrEqualsID) { - def msg = em"${hl("=")}, ${hl(">:")}, or ${hl("<:")} expected, but ${Tokens.showToken(found)} found" - - def explain = - em"""Type parameters and abstract types may be constrained by a type bound. - |Such type bounds limit the concrete values of the type variables and possibly - |reveal more information about the members of such types. - | - |A lower type bound ${hl("B >: A")} expresses that the type variable ${hl("B")} - |refers to a supertype of type ${hl("A")}. - | - |An upper type bound ${hl("T <: A")} declares that type variable ${hl("T")} - |refers to a subtype of type ${hl("A")}. - |""" - } - - class ClassAndCompanionNameClash(cls: Symbol, other: Symbol)(using Context) - extends NamingMsg(ClassAndCompanionNameClashID) { - def msg = - val name = cls.name.stripModuleClassSuffix - em"Name clash: both ${cls.owner} and its companion object defines $name" - def explain = - em"""|A ${cls.kindString} and its companion object cannot both define a ${hl("class")}, ${hl("trait")} or ${hl("object")} with the same name: - | - ${cls.owner} defines ${cls} - | - ${other.owner} defines ${other}""" - } - - class TailrecNotApplicable(symbol: Symbol)(using Context) - extends SyntaxMsg(TailrecNotApplicableID) { - def msg = { - val reason = - if (!symbol.is(Method)) em"$symbol isn't a method" - else if (symbol.is(Deferred)) em"$symbol is abstract" - else if (!symbol.isEffectivelyFinal) em"$symbol is neither ${hl("private")} nor ${hl("final")} so can be overridden" - else em"$symbol contains no recursive calls" - - s"TailRec optimisation not applicable, $reason" - } - def explain = "" - } - - class FailureToEliminateExistential(tp: Type, tp1: Type, tp2: Type, boundSyms: List[Symbol], classRoot: Symbol)(using Context) - extends Message(FailureToEliminateExistentialID) { - def kind = MessageKind.Compatibility - def msg = - val originalType = ctx.printer.dclsText(boundSyms, "; ").show - em"""An existential type that came from a Scala-2 classfile for $classRoot - |cannot be mapped accurately to a Scala-3 equivalent. - |original type : $tp forSome ${originalType} - |reduces to : $tp1 - |type used instead: $tp2 - |This choice can cause follow-on type errors or hide type errors. - |Proceed at own risk.""" - def explain = - em"""Existential types in their full generality are no longer supported. - |Scala-3 does applications of class types to wildcard type arguments. - |Other forms of existential types that come from Scala-2 classfiles - |are only approximated in a best-effort way.""" - } - - class OnlyFunctionsCanBeFollowedByUnderscore(tp: Type)(using Context) - extends SyntaxMsg(OnlyFunctionsCanBeFollowedByUnderscoreID) { - def msg = em"Only function types can be followed by ${hl("_")} but the current expression has type $tp" - def explain = - em"""The syntax ${hl("x _")} is no longer supported if ${hl("x")} is not a function. - |To convert to a function value, you need to explicitly write ${hl("() => x")}""" - } - - class MissingEmptyArgumentList(method: String)(using Context) - extends SyntaxMsg(MissingEmptyArgumentListID) { - def msg = em"$method must be called with ${hl("()")} argument" - def explain = { - val codeExample = - """def next(): T = ... - |next // is expanded to next()""" +class AmbiguousOverload(tree: tpd.Tree, val alternatives: List[SingleDenotation], pt: Type, addendum: String = "")( + implicit ctx: Context) +extends ReferenceMsg(AmbiguousOverloadID), NoDisambiguation { + private def all = if (alternatives.length == 2) "both" else "all" + def msg(using Context) = + i"""|Ambiguous overload. The ${err.overloadedAltsStr(alternatives)} + |$all match ${err.expectedTypeStr(pt)}$addendum""" + def explain(using Context) = + i"""|There are ${alternatives.length} methods that could be referenced as the compiler knows too little + |about the expected type. + |You may specify the expected type e.g. by + |- assigning it to a value with a specified type, or + |- adding a type ascription as in ${hl("instance.myMethod: String => Int")} + |""" +} + +class ReassignmentToVal(name: Name)(using Context) + extends TypeMsg(ReassignmentToValID) { + def msg(using Context) = i"""Reassignment to val $name""" + def explain(using Context) = + i"""|You can not assign a new value to $name as values can't be changed. + |Keep in mind that every statement has a value, so you may e.g. use + | ${hl("val")} $name ${hl("= if (condition) 2 else 5")} + |In case you need a reassignable name, you can declare it as + |variable + | ${hl("var")} $name ${hl("=")} ... + |""" +} + +class TypeDoesNotTakeParameters(tpe: Type, params: List[untpd.Tree])(using Context) + extends TypeMsg(TypeDoesNotTakeParametersID) { + private def fboundsAddendum(using Context) = + if tpe.typeSymbol.isAllOf(Provisional | TypeParam) then + "\n(Note that F-bounds of type parameters may not be type lambdas)" + else "" + def msg(using Context) = i"$tpe does not take type parameters$fboundsAddendum" + def explain(using Context) = + val ps = + if (params.size == 1) s"a type parameter ${params.head}" + else s"type parameters ${params.map(_.show).mkString(", ")}" + i"""You specified ${NoColor(ps)} for $tpe, which is not + |declared to take any. + |""" +} + +class VarValParametersMayNotBeCallByName(name: TermName, mutable: Boolean)(using Context) + extends SyntaxMsg(VarValParametersMayNotBeCallByNameID) { + def varOrVal = if mutable then hl("var") else hl("val") + def msg(using Context) = s"$varOrVal parameters may not be call-by-name" + def explain(using Context) = + i"""${hl("var")} and ${hl("val")} parameters of classes and traits may no be call-by-name. In case you + |want the parameter to be evaluated on demand, consider making it just a parameter + |and a ${hl("def")} in the class such as + | ${s"class MyClass(${name}Tick: => String) {"} + | ${s" def $name() = ${name}Tick"} + | ${hl("}")} + |""" +} + +class MissingTypeParameterFor(tpe: Type)(using Context) + extends SyntaxMsg(MissingTypeParameterForID) { + def msg(using Context) = + if tpe.derivesFrom(defn.AnyKindClass) + then i"$tpe cannot be used as a value type" + else i"Missing type parameter for $tpe" + def explain(using Context) = "" +} + +class MissingTypeParameterInTypeApp(tpe: Type)(using Context) + extends TypeMsg(MissingTypeParameterInTypeAppID) { + def numParams = tpe.typeParams.length + def parameters = if (numParams == 1) "parameter" else "parameters" + def msg(using Context) = i"Missing type $parameters for $tpe" + def explain(using Context) = i"A fully applied type is expected but $tpe takes $numParams $parameters" +} + +class MissingArgument(pname: Name, methString: String)(using Context) + extends TypeMsg(MissingArgumentID): + def msg(using Context) = + if pname.firstPart contains '$' then s"not enough arguments for $methString" + else s"missing argument for parameter $pname of $methString" + def explain(using Context) = "" + +class DoesNotConformToBound(tpe: Type, which: String, bound: Type)(using Context) + extends TypeMismatchMsg( + if which == "lower" then bound else tpe, + if which == "lower" then tpe else bound)(DoesNotConformToBoundID): + private def isBounds = tpe match + case TypeBounds(lo, hi) => lo ne hi + case _ => false + override def canExplain = !isBounds + def msg(using Context) = + if isBounds then + i"Type argument ${tpe} does not overlap with $which bound $bound" + else + i"Type argument ${tpe} does not conform to $which bound $bound" + +class DoesNotConformToSelfType(category: String, selfType: Type, cls: Symbol, + otherSelf: Type, relation: String, other: Symbol)( + implicit ctx: Context) + extends TypeMismatchMsg(selfType, otherSelf)(DoesNotConformToSelfTypeID) { + def msg(using Context) = i"""$category: self type $selfType of $cls does not conform to self type $otherSelf + |of $relation $other""" +} + +class DoesNotConformToSelfTypeCantBeInstantiated(tp: Type, selfType: Type)( + implicit ctx: Context) + extends TypeMismatchMsg(tp, selfType)(DoesNotConformToSelfTypeCantBeInstantiatedID) { + def msg(using Context) = i"""$tp does not conform to its self type $selfType; cannot be instantiated""" +} + +class IllegalParameterInit(found: Type, expected: Type, param: Symbol, cls: Symbol)(using Context) + extends TypeMismatchMsg(found, expected)(IllegalParameterInitID): + def msg(using Context) = + i"""illegal parameter initialization of $param. + | + | The argument passed for $param has type: $found + | but $cls expects $param to have type: $expected""" + +class AbstractMemberMayNotHaveModifier(sym: Symbol, flag: FlagSet)( + implicit ctx: Context) + extends SyntaxMsg(AbstractMemberMayNotHaveModifierID) { + def msg(using Context) = i"""${hl("abstract")} $sym may not have `${flag.flagsString}` modifier""" + def explain(using Context) = "" +} + +class TypesAndTraitsCantBeImplicit()(using Context) + extends SyntaxMsg(TypesAndTraitsCantBeImplicitID) { + def msg(using Context) = i"""${hl("implicit")} modifier cannot be used for types or traits""" + def explain(using Context) = "" +} + +class OnlyClassesCanBeAbstract(sym: Symbol)( + implicit ctx: Context) + extends SyntaxMsg(OnlyClassesCanBeAbstractID) { + def explain(using Context) = "" + def msg(using Context) = i"""${hl("abstract")} modifier can be used only for classes; it should be omitted for abstract members""" +} + +class AbstractOverrideOnlyInTraits(sym: Symbol)( + implicit ctx: Context) + extends SyntaxMsg(AbstractOverrideOnlyInTraitsID) { + def msg(using Context) = i"""${hl("abstract override")} modifier only allowed for members of traits""" + def explain(using Context) = "" +} + +class TraitsMayNotBeFinal(sym: Symbol)( + implicit ctx: Context) + extends SyntaxMsg(TraitsMayNotBeFinalID) { + def msg(using Context) = i"""$sym may not be ${hl("final")}""" + def explain(using Context) = + "A trait can never be final since it is abstract and must be extended to be useful." +} + +class NativeMembersMayNotHaveImplementation(sym: Symbol)( + implicit ctx: Context) + extends SyntaxMsg(NativeMembersMayNotHaveImplementationID) { + def msg(using Context) = i"""${hl("@native")} members may not have an implementation""" + def explain(using Context) = "" +} + +class TraitMayNotDefineNativeMethod(sym: Symbol)( + implicit ctx: Context) + extends SyntaxMsg(TraitMayNotDefineNativeMethodID) { + def msg(using Context) = i"""A trait cannot define a ${hl("@native")} method.""" + def explain(using Context) = "" +} + +class OnlyClassesCanHaveDeclaredButUndefinedMembers(sym: Symbol)( + implicit ctx: Context) + extends SyntaxMsg(OnlyClassesCanHaveDeclaredButUndefinedMembersID) { + + def msg(using Context) = i"""Declaration of $sym not allowed here: only classes can have declared but undefined members""" + def explain(using Context) = + if sym.is(Mutable) then "Note that variables need to be initialized to be defined." + else "" +} + +class CannotExtendAnyVal(sym: Symbol)(using Context) + extends SyntaxMsg(CannotExtendAnyValID) { + def msg(using Context) = i"""$sym cannot extend ${hl("AnyVal")}""" + def explain(using Context) = + i"""Only classes (not traits) are allowed to extend ${hl("AnyVal")}, but traits may extend + |${hl("Any")} to become ${Green("\"universal traits\"")} which may only have ${hl("def")} members. + |Universal traits can be mixed into classes that extend ${hl("AnyVal")}. + |""" +} - em"""Previously an empty argument list () was implicitly inserted when calling a nullary method without arguments. E.g. - | - |$codeExample - | - |In Dotty, this idiom is an error. The application syntax has to follow exactly the parameter syntax. - |Excluded from this rule are methods that are defined in Java or that override methods defined in Java.""" - } +class CannotExtendJavaEnum(sym: Symbol)(using Context) + extends SyntaxMsg(CannotExtendJavaEnumID) { + def msg(using Context) = i"""$sym cannot extend ${hl("java.lang.Enum")}: only enums defined with the ${hl("enum")} syntax can""" + def explain(using Context) = "" } - class DuplicateNamedTypeParameter(name: Name)(using Context) - extends SyntaxMsg(DuplicateNamedTypeParameterID) { - def msg = em"Type parameter $name was defined multiple times." - def explain = "" +class CannotExtendContextFunction(sym: Symbol)(using Context) + extends SyntaxMsg(CannotExtendFunctionID) { + def msg(using Context) = i"""$sym cannot extend a context function class""" + def explain(using Context) = "" } - class UndefinedNamedTypeParameter(undefinedName: Name, definedNames: List[Name])(using Context) - extends SyntaxMsg(UndefinedNamedTypeParameterID) { - def msg = em"Type parameter $undefinedName is undefined. Expected one of ${definedNames.map(_.show).mkString(", ")}." - def explain = "" +class JavaEnumParentArgs(parent: Type)(using Context) + extends TypeMsg(JavaEnumParentArgsID) { + def msg(using Context) = i"""not enough arguments for constructor Enum: ${hl("(name: String, ordinal: Int)")}: ${hl(parent.show)}""" + def explain(using Context) = "" } - class IllegalStartOfStatement(what: String, isModifier: Boolean, isStat: Boolean)(using Context) extends SyntaxMsg(IllegalStartOfStatementID) { - def msg = - if isStat then - "this kind of statement is not allowed here" - else - val addendum = if isModifier then ": this modifier is not allowed here" else "" - s"Illegal start of $what$addendum" - def explain = - i"""A statement is an import or export, a definition or an expression. - |Some statements are only allowed in certain contexts""" +class CannotHaveSameNameAs(sym: Symbol, cls: Symbol, reason: CannotHaveSameNameAs.Reason)(using Context) + extends NamingMsg(CannotHaveSameNameAsID) { + import CannotHaveSameNameAs._ + def reasonMessage(using Context): String = reason match { + case CannotBeOverridden => "class definitions cannot be overridden" + case DefinedInSelf(self) => + s"""cannot define ${sym.showKind} member with the same name as a ${cls.showKind} member in self reference ${self.name}. + |(Note: this can be resolved by using another name) + |""".stripMargin } - class TraitIsExpected(symbol: Symbol)(using Context) extends SyntaxMsg(TraitIsExpectedID) { - def msg = em"$symbol is not a trait" - def explain = { - val errorCodeExample = - """class A - |class B - | - |val a = new A with B // will fail with a compile error - class B is not a trait""".stripMargin - val codeExample = - """class A - |trait B - | - |val a = new A with B // compiles normally""".stripMargin + def msg(using Context) = i"""$sym cannot have the same name as ${cls.showLocated} -- """ + reasonMessage + def explain(using Context) = "" +} +object CannotHaveSameNameAs { + sealed trait Reason + case object CannotBeOverridden extends Reason + case class DefinedInSelf(self: tpd.ValDef) extends Reason +} + +class ValueClassesMayNotDefineInner(valueClass: Symbol, inner: Symbol)(using Context) + extends SyntaxMsg(ValueClassesMayNotDefineInnerID) { + def msg(using Context) = i"""Value classes may not define an inner class""" + def explain(using Context) = "" +} + +class ValueClassesMayNotDefineNonParameterField(valueClass: Symbol, field: Symbol)(using Context) + extends SyntaxMsg(ValueClassesMayNotDefineNonParameterFieldID) { + def msg(using Context) = i"""Value classes may not define non-parameter field""" + def explain(using Context) = "" +} + +class ValueClassesMayNotDefineASecondaryConstructor(valueClass: Symbol, constructor: Symbol)(using Context) + extends SyntaxMsg(ValueClassesMayNotDefineASecondaryConstructorID) { + def msg(using Context) = i"""Value classes may not define a secondary constructor""" + def explain(using Context) = "" +} + +class ValueClassesMayNotContainInitalization(valueClass: Symbol)(using Context) + extends SyntaxMsg(ValueClassesMayNotContainInitalizationID) { + def msg(using Context) = i"""Value classes may not contain initialization statements""" + def explain(using Context) = "" +} + +class ValueClassesMayNotBeAbstract(valueClass: Symbol)(using Context) + extends SyntaxMsg(ValueClassesMayNotBeAbstractID) { + def msg(using Context) = i"""Value classes may not be ${hl("abstract")}""" + def explain(using Context) = "" +} + +class ValueClassesMayNotBeContainted(valueClass: Symbol)(using Context) + extends SyntaxMsg(ValueClassesMayNotBeContaintedID) { + private def localOrMember = if (valueClass.owner.isTerm) "local class" else "member of another class" + def msg(using Context) = s"""Value classes may not be a $localOrMember""" + def explain(using Context) = "" +} + +class ValueClassesMayNotWrapAnotherValueClass(valueClass: Symbol)(using Context) + extends SyntaxMsg(ValueClassesMayNotWrapAnotherValueClassID) { + def msg(using Context) = """A value class may not wrap another user-defined value class""" + def explain(using Context) = "" +} + +class ValueClassParameterMayNotBeAVar(valueClass: Symbol, param: Symbol)(using Context) + extends SyntaxMsg(ValueClassParameterMayNotBeAVarID) { + def msg(using Context) = i"""A value class parameter may not be a ${hl("var")}""" + def explain(using Context) = + i"""A value class must have exactly one ${hl("val")} parameter.""" +} + +class ValueClassNeedsOneValParam(valueClass: Symbol)(using Context) + extends SyntaxMsg(ValueClassNeedsExactlyOneValParamID) { + def msg(using Context) = i"""Value class needs one ${hl("val")} parameter""" + def explain(using Context) = "" +} + +class ValueClassParameterMayNotBeCallByName(valueClass: Symbol, param: Symbol)(using Context) + extends SyntaxMsg(ValueClassParameterMayNotBeCallByNameID) { + def msg(using Context) = s"Value class parameter `${param.name}` may not be call-by-name" + def explain(using Context) = "" +} + +class SuperCallsNotAllowedInlineable(symbol: Symbol)(using Context) + extends SyntaxMsg(SuperCallsNotAllowedInlineableID) { + def msg(using Context) = i"Super call not allowed in inlineable $symbol" + def explain(using Context) = "Method inlining prohibits calling superclass methods, as it may lead to confusion about which super is being called." +} + +class NotAPath(tp: Type, usage: String)(using Context) extends TypeMsg(NotAPathID): + def msg(using Context) = i"$tp is not a valid $usage, since it is not an immutable path" + def explain(using Context) = + i"""An immutable path is + | - a reference to an immutable value, or + | - a reference to `this`, or + | - a selection of an immutable path with an immutable value.""" + +class WrongNumberOfParameters(expected: Int)(using Context) + extends SyntaxMsg(WrongNumberOfParametersID) { + def msg(using Context) = s"Wrong number of parameters, expected: $expected" + def explain(using Context) = "" +} + +class DuplicatePrivateProtectedQualifier()(using Context) + extends SyntaxMsg(DuplicatePrivateProtectedQualifierID) { + def msg(using Context) = "Duplicate private/protected qualifier" + def explain(using Context) = + i"It is not allowed to combine `private` and `protected` modifiers even if they are qualified to different scopes" +} + +class ExpectedStartOfTopLevelDefinition()(using Context) + extends SyntaxMsg(ExpectedStartOfTopLevelDefinitionID) { + def msg(using Context) = "Expected start of definition" + def explain(using Context) = + i"You have to provide either ${hl("class")}, ${hl("trait")}, ${hl("object")}, or ${hl("enum")} definitions after qualifiers" +} + +class NoReturnFromInlineable(owner: Symbol)(using Context) + extends SyntaxMsg(NoReturnFromInlineableID) { + def msg(using Context) = i"No explicit ${hl("return")} allowed from inlineable $owner" + def explain(using Context) = + i"""Methods marked with ${hl("inline")} modifier may not use ${hl("return")} statements. + |Instead, you should rely on the last expression's value being + |returned from a method. + |""" +} + +class ReturnOutsideMethodDefinition(owner: Symbol)(using Context) + extends SyntaxMsg(ReturnOutsideMethodDefinitionID) { + def msg(using Context) = i"${hl("return")} outside method definition" + def explain(using Context) = + i"""You used ${hl("return")} in ${owner}. + |${hl("return")} is a keyword and may only be used within method declarations. + |""" +} + +class ExtendFinalClass(clazz:Symbol, finalClazz: Symbol)(using Context) + extends SyntaxMsg(ExtendFinalClassID) { + def msg(using Context) = i"$clazz cannot extend ${hl("final")} $finalClazz" + def explain(using Context) = + i"""A class marked with the ${hl("final")} keyword cannot be extended""" +} + +class ExpectedTypeBoundOrEquals(found: Token)(using Context) + extends SyntaxMsg(ExpectedTypeBoundOrEqualsID) { + def msg(using Context) = i"${hl("=")}, ${hl(">:")}, or ${hl("<:")} expected, but ${Tokens.showToken(found)} found" + + def explain(using Context) = + i"""Type parameters and abstract types may be constrained by a type bound. + |Such type bounds limit the concrete values of the type variables and possibly + |reveal more information about the members of such types. + | + |A lower type bound ${hl("B >: A")} expresses that the type variable ${hl("B")} + |refers to a supertype of type ${hl("A")}. + | + |An upper type bound ${hl("T <: A")} declares that type variable ${hl("T")} + |refers to a subtype of type ${hl("A")}. + |""" +} + +class ClassAndCompanionNameClash(cls: Symbol, other: Symbol)(using Context) + extends NamingMsg(ClassAndCompanionNameClashID) { + def msg(using Context) = + val name = cls.name.stripModuleClassSuffix + i"Name clash: both ${cls.owner} and its companion object defines $name" + def explain(using Context) = + i"""|A ${cls.kindString} and its companion object cannot both define a ${hl("class")}, ${hl("trait")} or ${hl("object")} with the same name: + | - ${cls.owner} defines ${cls} + | - ${other.owner} defines ${other}""" +} + +class TailrecNotApplicable(symbol: Symbol)(using Context) + extends SyntaxMsg(TailrecNotApplicableID) { + def msg(using Context) = { + val reason = + if !symbol.is(Method) then i"$symbol isn't a method" + else if symbol.is(Deferred) then i"$symbol is abstract" + else if !symbol.isEffectivelyFinal then i"$symbol is neither ${hl("private")} nor ${hl("final")} so can be overridden" + else i"$symbol contains no recursive calls" + + s"TailRec optimisation not applicable, $reason" + } + def explain(using Context) = "" +} + +class FailureToEliminateExistential(tp: Type, tp1: Type, tp2: Type, boundSyms: List[Symbol], classRoot: Symbol)(using Context) + extends Message(FailureToEliminateExistentialID) { + def kind = MessageKind.Compatibility + def msg(using Context) = + val originalType = ctx.printer.dclsText(boundSyms, "; ").show + i"""An existential type that came from a Scala-2 classfile for $classRoot + |cannot be mapped accurately to a Scala-3 equivalent. + |original type : $tp forSome ${originalType} + |reduces to : $tp1 + |type used instead: $tp2 + |This choice can cause follow-on type errors or hide type errors. + |Proceed at own risk.""" + def explain(using Context) = + i"""Existential types in their full generality are no longer supported. + |Scala-3 does applications of class types to wildcard type arguments. + |Other forms of existential types that come from Scala-2 classfiles + |are only approximated in a best-effort way.""" +} + +class OnlyFunctionsCanBeFollowedByUnderscore(tp: Type)(using Context) + extends SyntaxMsg(OnlyFunctionsCanBeFollowedByUnderscoreID) { + def msg(using Context) = i"Only function types can be followed by ${hl("_")} but the current expression has type $tp" + def explain(using Context) = + i"""The syntax ${hl("x _")} is no longer supported if ${hl("x")} is not a function. + |To convert to a function value, you need to explicitly write ${hl("() => x")}""" +} + +class MissingEmptyArgumentList(method: String)(using Context) + extends SyntaxMsg(MissingEmptyArgumentListID) { + def msg(using Context) = i"$method must be called with ${hl("()")} argument" + def explain(using Context) = { + val codeExample = + """def next(): T = ... + |next // is expanded to next()""" + + i"""Previously an empty argument list () was implicitly inserted when calling a nullary method without arguments. E.g. + | + |$codeExample + | + |In Dotty, this idiom is an error. The application syntax has to follow exactly the parameter syntax. + |Excluded from this rule are methods that are defined in Java or that override methods defined in Java.""" + } +} + +class DuplicateNamedTypeParameter(name: Name)(using Context) + extends SyntaxMsg(DuplicateNamedTypeParameterID) { + def msg(using Context) = i"Type parameter $name was defined multiple times." + def explain(using Context) = "" +} + +class UndefinedNamedTypeParameter(undefinedName: Name, definedNames: List[Name])(using Context) + extends SyntaxMsg(UndefinedNamedTypeParameterID) { + def msg(using Context) = i"Type parameter $undefinedName is undefined. Expected one of ${definedNames.map(_.show).mkString(", ")}." + def explain(using Context) = "" +} + +class IllegalStartOfStatement(what: String, isModifier: Boolean, isStat: Boolean)(using Context) extends SyntaxMsg(IllegalStartOfStatementID) { + def msg(using Context) = + if isStat then + "this kind of statement is not allowed here" + else + val addendum = if isModifier then ": this modifier is not allowed here" else "" + s"Illegal start of $what$addendum" + def explain(using Context) = + i"""A statement is an import or export, a definition or an expression. + |Some statements are only allowed in certain contexts""" +} + +class TraitIsExpected(symbol: Symbol)(using Context) extends SyntaxMsg(TraitIsExpectedID) { + def msg(using Context) = i"$symbol is not a trait" + def explain(using Context) = { + val errorCodeExample = + """class A + |class B + | + |val a = new A with B // will fail with a compile error - class B is not a trait""".stripMargin + val codeExample = + """class A + |trait B + | + |val a = new A with B // compiles normally""".stripMargin - em"""Only traits can be mixed into classes using a ${hl("with")} keyword. - |Consider the following example: - | - |$errorCodeExample - | - |The example mentioned above would fail because B is not a trait. - |But if you make B a trait it will be compiled without any errors: - | - |$codeExample - |""" - } + i"""Only traits can be mixed into classes using a ${hl("with")} keyword. + |Consider the following example: + | + |$errorCodeExample + | + |The example mentioned above would fail because B is not a trait. + |But if you make B a trait it will be compiled without any errors: + | + |$codeExample + |""" } +} - class TraitRedefinedFinalMethodFromAnyRef(method: Symbol)(using Context) extends SyntaxMsg(TraitRedefinedFinalMethodFromAnyRefID) { - def msg = em"Traits cannot redefine final $method from ${hl("class AnyRef")}." - def explain = "" - } +class TraitRedefinedFinalMethodFromAnyRef(method: Symbol)(using Context) extends SyntaxMsg(TraitRedefinedFinalMethodFromAnyRefID) { + def msg(using Context) = i"Traits cannot redefine final $method from ${hl("class AnyRef")}." + def explain(using Context) = "" +} - class AlreadyDefined(name: Name, owner: Symbol, conflicting: Symbol)(using Context) extends NamingMsg(AlreadyDefinedID): - private def where: String = +class AlreadyDefined(name: Name, owner: Symbol, conflicting: Symbol)(using Context) +extends NamingMsg(AlreadyDefinedID): + def msg(using Context) = + def where: String = if conflicting.effectiveOwner.is(Package) && conflicting.associatedFile != null then i" in ${conflicting.associatedFile}" else if conflicting.owner == owner then "" else i" in ${conflicting.owner}" - private def note = + def note = if owner.is(Method) || conflicting.is(Method) then "\n\nNote that overloaded methods must all be defined in the same group of toplevel definitions" else "" - def msg = - if conflicting.isTerm != name.isTermName then - em"$name clashes with $conflicting$where; the two must be defined together" - else - em"$name is already defined as $conflicting$where$note" - def explain = "" - - class PackageNameAlreadyDefined(pkg: Symbol)(using Context) extends NamingMsg(PackageNameAlreadyDefinedID) { - lazy val (where, or) = - if pkg.associatedFile == null then ("", "") - else (s" in ${pkg.associatedFile}", " or delete the containing class file") - def msg = em"""${pkg.name} is the name of $pkg$where. - |It cannot be used at the same time as the name of a package.""" - def explain = - em"""An ${hl("object")} or other toplevel definition cannot have the same name as an existing ${hl("package")}. - |Rename either one of them$or.""" - } - - class UnapplyInvalidNumberOfArguments(qual: untpd.Tree, argTypes: List[Type])(using Context) - extends SyntaxMsg(UnapplyInvalidNumberOfArgumentsID) { - def msg = em"Wrong number of argument patterns for $qual; expected: ($argTypes%, %)" - def explain = - em"""The Unapply method of $qual was used with incorrect number of arguments. - |Expected usage would be something like: - |case $qual(${argTypes.map(_ => '_')}%, %) => ... - | - |where subsequent arguments would have following types: ($argTypes%, %). - |""".stripMargin - } - - class UnapplyInvalidReturnType(unapplyResult: Type, unapplyName: Name)(using Context) - extends DeclarationMsg(UnapplyInvalidReturnTypeID) { - def msg = - val addendum = - if Feature.migrateTo3 && unapplyName == nme.unapplySeq - then "\nYou might want to try to rewrite the extractor to use `unapply` instead." - else "" - em"""| ${Red(i"$unapplyResult")} is not a valid result type of an $unapplyName method of an ${Magenta("extractor")}.$addendum""" - def explain = if (unapplyName.show == "unapply") - em""" - |To be used as an extractor, an unapply method has to return a type that either: - | - has members ${Magenta("isEmpty: Boolean")} and ${Magenta("get: S")} (usually an ${Green("Option[S]")}) - | - is a ${Green("Boolean")} - | - is a ${Green("Product")} (like a ${Magenta("Tuple2[T1, T2]")}) - | - |class A(val i: Int) - | - |object B { - | def unapply(a: A): ${Green("Option[Int]")} = Some(a.i) - |} - | - |object C { - | def unapply(a: A): ${Green("Boolean")} = a.i == 2 - |} - | - |object D { - | def unapply(a: A): ${Green("(Int, Int)")} = (a.i, a.i) - |} - | - |object Test { - | def test(a: A) = a match { - | ${Magenta("case B(1)")} => 1 - | ${Magenta("case a @ C()")} => 2 - | ${Magenta("case D(3, 3)")} => 3 - | } - |} - """.stripMargin + if conflicting.isTerm != name.isTermName then + i"$name clashes with $conflicting$where; the two must be defined together" else - em""" - |To be used as an extractor, an unapplySeq method has to return a type which has members - |${Magenta("isEmpty: Boolean")} and ${Magenta("get: S")} where ${Magenta("S <: Seq[V]")} (usually an ${Green("Option[Seq[V]]")}): - | - |object CharList { - | def unapplySeq(s: String): ${Green("Option[Seq[Char]")} = Some(s.toList) - | - | "example" match { - | ${Magenta("case CharList(c1, c2, c3, c4, _, _, _)")} => - | println(s"$$c1,$$c2,$$c3,$$c4") - | case _ => - | println("Expected *exactly* 7 characters!") - | } - |} - """.stripMargin - } - - class StaticFieldsOnlyAllowedInObjects(member: Symbol)(using Context) extends SyntaxMsg(StaticFieldsOnlyAllowedInObjectsID) { - def msg = em"${hl("@static")} $member in ${member.owner} must be defined inside a static ${hl("object")}." - def explain = - em"${hl("@static")} members are only allowed inside objects." - } - - class StaticFieldsShouldPrecedeNonStatic(member: Symbol, defns: List[tpd.Tree])(using Context) extends SyntaxMsg(StaticFieldsShouldPrecedeNonStaticID) { - def msg = em"${hl("@static")} $member in ${member.owner} must be defined before non-static fields." - def explain = { - val nonStatics = defns.takeWhile(_.symbol != member).take(3).filter(_.isInstanceOf[tpd.ValDef]) - val codeExample = s"""object ${member.owner.name.firstPart} { - | @static ${member} = ... - | ${nonStatics.map(m => s"${m.symbol} = ...").mkString("\n ")} - | ... - |}""" - em"""The fields annotated with @static should precede any non @static fields. - |This ensures that we do not introduce surprises for users in initialization order of this class. - |Static field are initialized when class loading the code of Foo. - |Non static fields are only initialized the first time that Foo is accessed. - | - |The definition of ${member.name} should have been before the non ${hl("@static val")}s: - |$codeExample + i"$name is already defined as $conflicting$where$note" + def explain(using Context) = "" + +class PackageNameAlreadyDefined(pkg: Symbol)(using Context) extends NamingMsg(PackageNameAlreadyDefinedID) { + def msg(using Context) = + def where = if pkg.associatedFile == null then "" else s" in ${pkg.associatedFile}" + i"""${pkg.name} is the name of $pkg$where. + |It cannot be used at the same time as the name of a package.""" + def explain(using Context) = + def or = if pkg.associatedFile == null then "" else " or delete the containing class file" + i"""An ${hl("object")} or other toplevel definition cannot have the same name as an existing ${hl("package")}. + |Rename either one of them$or.""" +} + +class UnapplyInvalidNumberOfArguments(qual: untpd.Tree, argTypes: List[Type])(using Context) + extends SyntaxMsg(UnapplyInvalidNumberOfArgumentsID) { + def msg(using Context) = i"Wrong number of argument patterns for $qual; expected: ($argTypes%, %)" + def explain(using Context) = + i"""The Unapply method of $qual was used with incorrect number of arguments. + |Expected usage would be something like: + |case $qual(${argTypes.map(_ => '_')}%, %) => ... + | + |where subsequent arguments would have following types: ($argTypes%, %). |""" +} + +class UnapplyInvalidReturnType(unapplyResult: Type, unapplyName: Name)(using Context) + extends DeclarationMsg(UnapplyInvalidReturnTypeID) { + def msg(using Context) = + val addendum = + if Feature.migrateTo3 && unapplyName == nme.unapplySeq + then "\nYou might want to try to rewrite the extractor to use `unapply` instead." + else "" + i"""| ${Red(i"$unapplyResult")} is not a valid result type of an $unapplyName method of an ${Magenta("extractor")}.$addendum""" + def explain(using Context) = if (unapplyName.show == "unapply") + i""" + |To be used as an extractor, an unapply method has to return a type that either: + | - has members ${Magenta("isEmpty: Boolean")} and ${Magenta("get: S")} (usually an ${Green("Option[S]")}) + | - is a ${Green("Boolean")} + | - is a ${Green("Product")} (like a ${Magenta("Tuple2[T1, T2]")}) + | + |class A(val i: Int) + | + |object B { + | def unapply(a: A): ${Green("Option[Int]")} = Some(a.i) + |} + | + |object C { + | def unapply(a: A): ${Green("Boolean")} = a.i == 2 + |} + | + |object D { + | def unapply(a: A): ${Green("(Int, Int)")} = (a.i, a.i) + |} + | + |object Test { + | def test(a: A) = a match { + | ${Magenta("case B(1)")} => 1 + | ${Magenta("case a @ C()")} => 2 + | ${Magenta("case D(3, 3)")} => 3 + | } + |} + """ + else + i""" + |To be used as an extractor, an unapplySeq method has to return a type which has members + |${Magenta("isEmpty: Boolean")} and ${Magenta("get: S")} where ${Magenta("S <: Seq[V]")} (usually an ${Green("Option[Seq[V]]")}): + | + |object CharList { + | def unapplySeq(s: String): ${Green("Option[Seq[Char]")} = Some(s.toList) + | + | "example" match { + | ${Magenta("case CharList(c1, c2, c3, c4, _, _, _)")} => + | println(s"$$c1,$$c2,$$c3,$$c4") + | case _ => + | println("Expected *exactly* 7 characters!") + | } + |} + """ +} + +class StaticFieldsOnlyAllowedInObjects(member: Symbol)(using Context) extends SyntaxMsg(StaticFieldsOnlyAllowedInObjectsID) { + def msg(using Context) = i"${hl("@static")} $member in ${member.owner} must be defined inside a static ${hl("object")}." + def explain(using Context) = + i"${hl("@static")} members are only allowed inside objects." +} + +class StaticFieldsShouldPrecedeNonStatic(member: Symbol, defns: List[tpd.Tree])(using Context) extends SyntaxMsg(StaticFieldsShouldPrecedeNonStaticID) { + def msg(using Context) = i"${hl("@static")} $member in ${member.owner} must be defined before non-static fields." + def explain(using Context) = { + val nonStatics = defns.takeWhile(_.symbol != member).take(3).filter(_.isInstanceOf[tpd.ValDef]) + val codeExample = s"""object ${member.owner.name.firstPart} { + | @static ${member} = ... + | ${nonStatics.map(m => s"${m.symbol} = ...").mkString("\n ")} + | ... + |}""" + i"""The fields annotated with @static should precede any non @static fields. + |This ensures that we do not introduce surprises for users in initialization order of this class. + |Static field are initialized when class loading the code of Foo. + |Non static fields are only initialized the first time that Foo is accessed. + | + |The definition of ${member.name} should have been before the non ${hl("@static val")}s: + |$codeExample + |""" + } +} + +class CyclicInheritance(symbol: Symbol, addendum: -> String)(using Context) extends SyntaxMsg(CyclicInheritanceID) { + def msg(using Context) = i"Cyclic inheritance: $symbol extends itself$addendum" + def explain(using Context) = { + val codeExample = "class A extends A" + + i"""Cyclic inheritance is prohibited in Dotty. + |Consider the following example: + | + |$codeExample + | + |The example mentioned above would fail because this type of inheritance hierarchy + |creates a "cycle" where a not yet defined class A extends itself which makes + |impossible to instantiate an object of this class""" + } +} + +class BadSymbolicReference(denot: SymDenotation)(using Context) +extends ReferenceMsg(BadSymbolicReferenceID) { + def msg(using Context) = { + val denotationOwner = denot.owner + val denotationName = ctx.fresh.setSetting(ctx.settings.YdebugNames, true).printer.nameString(denot.name) + val file = denot.symbol.associatedFile + val (location, src) = + if (file != null) (s" in $file", file.toString) + else ("", "the signature") + + i"""Bad symbolic reference. A signature$location + |refers to $denotationName in ${denotationOwner.showKind} ${denotationOwner.showFullName} which is not available. + |It may be completely missing from the current classpath, or the version on + |the classpath might be incompatible with the version used when compiling $src.""" + } + + def explain(using Context) = "" +} + +class UnableToExtendSealedClass(pclazz: Symbol)(using Context) extends SyntaxMsg(UnableToExtendSealedClassID) { + def msg(using Context) = i"Cannot extend ${hl("sealed")} $pclazz in a different source file" + def explain(using Context) = "A sealed class or trait can only be extended in the same file as its declaration" +} + +class SymbolHasUnparsableVersionNumber(symbol: Symbol, errorMessage: String)(using Context) +extends SyntaxMsg(SymbolHasUnparsableVersionNumberID) { + def msg(using Context) = i"${symbol.showLocated} has an unparsable version number: $errorMessage" + def explain(using Context) = + i"""The ${symbol.showLocated} is marked with ${hl("@migration")} indicating it has changed semantics + |between versions and the ${hl("-Xmigration")} settings is used to warn about constructs + |whose behavior may have changed since version change.""" +} + +class SymbolChangedSemanticsInVersion( + symbol: Symbol, + migrationVersion: ScalaVersion, + migrationMessage: String +)(using Context) extends SyntaxMsg(SymbolChangedSemanticsInVersionID) { + def msg(using Context) = i"${symbol.showLocated} has changed semantics in version $migrationVersion: $migrationMessage" + def explain(using Context) = + i"""The ${symbol.showLocated} is marked with ${hl("@migration")} indicating it has changed semantics + |between versions and the ${hl("-Xmigration")} settings is used to warn about constructs + |whose behavior may have changed since version change.""" +} + +class UnableToEmitSwitch()(using Context) +extends SyntaxMsg(UnableToEmitSwitchID) { + def msg(using Context) = i"Could not emit switch for ${hl("@switch")} annotated match" + def explain(using Context) = { + val codeExample = + """val ConstantB = 'B' + |final val ConstantC = 'C' + |def tokenMe(ch: Char) = (ch: @switch) match { + | case '\t' | '\n' => 1 + | case 'A' => 2 + | case ConstantB => 3 // a non-literal may prevent switch generation: this would not compile + | case ConstantC => 4 // a constant value is allowed + | case _ => 5 + |}""".stripMargin + + i"""If annotated with ${hl("@switch")}, the compiler will verify that the match has been compiled to a + |tableswitch or lookupswitch and issue an error if it instead compiles into a series of conditional + |expressions. Example usage: + | + |$codeExample + | + |The compiler will not apply the optimisation if: + |- the matched value is not of type ${hl("Int")}, ${hl("Byte")}, ${hl("Short")} or ${hl("Char")} + |- the matched value is not a constant literal + |- there are less than three cases""" + } +} + +class MissingCompanionForStatic(member: Symbol)(using Context) +extends SyntaxMsg(MissingCompanionForStaticID) { + def msg(using Context) = i"${member.owner} does not have a companion class" + def explain(using Context) = + i"An object that contains ${hl("@static")} members must have a companion class." +} + +class PolymorphicMethodMissingTypeInParent(rsym: Symbol, parentSym: Symbol)(using Context) +extends SyntaxMsg(PolymorphicMethodMissingTypeInParentID) { + def msg(using Context) = i"Polymorphic refinement $rsym without matching type in parent $parentSym is no longer allowed" + def explain(using Context) = + i"""Polymorphic $rsym is not allowed in the structural refinement of $parentSym because + |$rsym does not override any method in $parentSym. Structural refinement does not allow for + |polymorphic methods.""" +} + +class ParamsNoInline(owner: Symbol)(using Context) + extends SyntaxMsg(ParamsNoInlineID) { + def msg(using Context) = i"""${hl("inline")} modifier can only be used for parameters of inline methods""" + def explain(using Context) = "" +} + +class JavaSymbolIsNotAValue(symbol: Symbol)(using Context) extends TypeMsg(JavaSymbolIsNotAValueID) { + def msg(using Context) = + val kind = + if symbol is Package then i"$symbol" + else i"Java defined ${hl("class " + symbol.name)}" + s"$kind is not a value" + def explain(using Context) = "" +} + +class DoubleDefinition(decl: Symbol, previousDecl: Symbol, base: Symbol)(using Context) +extends NamingMsg(DoubleDefinitionID) { + def msg(using Context) = { + def nameAnd = if (decl.name != previousDecl.name) " name and" else "" + def erasedType = if ctx.erasedTypes then i" ${decl.info}" else "" + def details(using Context): String = + if (decl.isRealMethod && previousDecl.isRealMethod) { + import Signature.MatchDegree._ + + // compare the signatures when both symbols represent methods + decl.signature.matchDegree(previousDecl.signature) match { + case NoMatch => + // If the signatures don't match at all at the current phase, then + // they might match after erasure. + if ctx.phase.id <= elimErasedValueTypePhase.id then + atPhase(elimErasedValueTypePhase.next)(details) + else + "" // shouldn't be reachable + case ParamMatch => + "have matching parameter types." + case MethodNotAMethodMatch => + "neither has parameters." + case FullMatch => + val hint = + if !decl.hasAnnotation(defn.TargetNameAnnot) + && !previousDecl.hasAnnotation(defn.TargetNameAnnot) + then + i""" + | + |Consider adding a @targetName annotation to one of the conflicting definitions + |for disambiguation.""" + else "" + i"have the same$nameAnd type$erasedType after erasure.$hint" + } + } + else "" + def symLocation(sym: Symbol) = { + val lineDesc = + if (sym.span.exists && sym.span != sym.owner.span) + s" at line ${sym.srcPos.line + 1}" + else "" + i"in ${sym.owner}${lineDesc}" } - } - - class CyclicInheritance(symbol: Symbol, addendum: -> String)(using Context) extends SyntaxMsg(CyclicInheritanceID) { - def msg = em"Cyclic inheritance: $symbol extends itself$addendum" - def explain = { - val codeExample = "class A extends A" - - em"""Cyclic inheritance is prohibited in Dotty. - |Consider the following example: - | - |$codeExample - | - |The example mentioned above would fail because this type of inheritance hierarchy - |creates a "cycle" where a not yet defined class A extends itself which makes - |impossible to instantiate an object of this class""" - } - } - - class BadSymbolicReference(denot: SymDenotation)(using Context) - extends ReferenceMsg(BadSymbolicReferenceID) { - def msg = { - val denotationOwner = denot.owner - val denotationName = ctx.fresh.setSetting(ctx.settings.YdebugNames, true).printer.nameString(denot.name) - val file = denot.symbol.associatedFile - val (location, src) = - if (file != null) (s" in $file", file.toString) - else ("", "the signature") - - em"""Bad symbolic reference. A signature$location - |refers to $denotationName in ${denotationOwner.showKind} ${denotationOwner.showFullName} which is not available. - |It may be completely missing from the current classpath, or the version on - |the classpath might be incompatible with the version used when compiling $src.""" - } - - def explain = "" - } - - class UnableToExtendSealedClass(pclazz: Symbol)(using Context) extends SyntaxMsg(UnableToExtendSealedClassID) { - def msg = em"Cannot extend ${hl("sealed")} $pclazz in a different source file" - def explain = "A sealed class or trait can only be extended in the same file as its declaration" - } - - class SymbolHasUnparsableVersionNumber(symbol: Symbol, errorMessage: String)(using Context) - extends SyntaxMsg(SymbolHasUnparsableVersionNumberID) { - def msg = em"${symbol.showLocated} has an unparsable version number: $errorMessage" - def explain = - em"""The ${symbol.showLocated} is marked with ${hl("@migration")} indicating it has changed semantics - |between versions and the ${hl("-Xmigration")} settings is used to warn about constructs - |whose behavior may have changed since version change.""" - } - - class SymbolChangedSemanticsInVersion( - symbol: Symbol, - migrationVersion: ScalaVersion, - migrationMessage: String - )(using Context) extends SyntaxMsg(SymbolChangedSemanticsInVersionID) { - def msg = em"${symbol.showLocated} has changed semantics in version $migrationVersion: $migrationMessage" - def explain = - em"""The ${symbol.showLocated} is marked with ${hl("@migration")} indicating it has changed semantics - |between versions and the ${hl("-Xmigration")} settings is used to warn about constructs - |whose behavior may have changed since version change.""" - } - - class UnableToEmitSwitch()(using Context) - extends SyntaxMsg(UnableToEmitSwitchID) { - def msg = em"Could not emit switch for ${hl("@switch")} annotated match" - def explain = { - val codeExample = - """val ConstantB = 'B' - |final val ConstantC = 'C' - |def tokenMe(ch: Char) = (ch: @switch) match { - | case '\t' | '\n' => 1 - | case 'A' => 2 - | case ConstantB => 3 // a non-literal may prevent switch generation: this would not compile - | case ConstantC => 4 // a constant value is allowed - | case _ => 5 - |}""".stripMargin + val clashDescription = + if (decl.owner eq previousDecl.owner) + "Double definition" + else if ((decl.owner eq base) || (previousDecl eq base)) + "Name clash between defined and inherited member" + else + "Name clash between inherited members" - em"""If annotated with ${hl("@switch")}, the compiler will verify that the match has been compiled to a - |tableswitch or lookupswitch and issue an error if it instead compiles into a series of conditional - |expressions. Example usage: - | - |$codeExample - | - |The compiler will not apply the optimisation if: - |- the matched value is not of type ${hl("Int")}, ${hl("Byte")}, ${hl("Short")} or ${hl("Char")} - |- the matched value is not a constant literal - |- there are less than three cases""" + atPhase(typerPhase) { + i"""$clashDescription: + |${previousDecl.showDcl} ${symLocation(previousDecl)} and + |${decl.showDcl} ${symLocation(decl)} + |""" + } + details + } + def explain(using Context) = "" +} + +class ImportRenamedTwice(ident: untpd.Ident)(using Context) extends SyntaxMsg(ImportRenamedTwiceID) { + def msg(using Context) = s"${ident.show} is renamed twice on the same import line." + def explain(using Context) = "" +} + +class TypeTestAlwaysDiverges(scrutTp: Type, testTp: Type)(using Context) extends SyntaxMsg(TypeTestAlwaysDivergesID) { + def msg(using Context) = + s"This type test will never return a result since the scrutinee type ${scrutTp.show} does not contain any value." + def explain(using Context) = "" +} + +// Relative of CyclicReferenceInvolvingImplicit and RecursiveValueNeedsResultType +class TermMemberNeedsResultTypeForImplicitSearch(cycleSym: Symbol)(using Context) + extends CyclicMsg(TermMemberNeedsNeedsResultTypeForImplicitSearchID) { + def msg(using Context) = i"""$cycleSym needs result type because its right-hand side attempts implicit search""" + def explain(using Context) = + i"""|The right hand-side of $cycleSym's definition requires an implicit search at the highlighted position. + |To avoid this error, give `$cycleSym` an explicit type. + |""" +} + +class ClassCannotExtendEnum(cls: Symbol, parent: Symbol)(using Context) extends SyntaxMsg(ClassCannotExtendEnumID) { + def msg(using Context) = i"""$cls in ${cls.owner} extends enum ${parent.name}, but extending enums is prohibited.""" + def explain(using Context) = "" +} + +class NotAnExtractor(tree: untpd.Tree)(using Context) extends SyntaxMsg(NotAnExtractorID) { + def msg(using Context) = i"$tree cannot be used as an extractor in a pattern because it lacks an unapply or unapplySeq method" + def explain(using Context) = + i"""|An ${hl("unapply")} method should be defined in an ${hl("object")} as follow: + | - If it is just a test, return a ${hl("Boolean")}. For example ${hl("case even()")} + | - If it returns a single sub-value of type T, return an ${hl("Option[T]")} + | - If it returns several sub-values T1,...,Tn, group them in an optional tuple ${hl("Option[(T1,...,Tn)]")} + | + |Sometimes, the number of sub-values isn't fixed and we would like to return a sequence. + |For this reason, you can also define patterns through ${hl("unapplySeq")} which returns ${hl("Option[Seq[T]]")}. + |This mechanism is used for instance in pattern ${hl("case List(x1, ..., xn)")}""" +} + +class MemberWithSameNameAsStatic()(using Context) + extends SyntaxMsg(MemberWithSameNameAsStaticID) { + def msg(using Context) = i"Companion classes cannot define members with same name as a ${hl("@static")} member" + def explain(using Context) = "" +} + +class PureExpressionInStatementPosition(stat: untpd.Tree, val exprOwner: Symbol)(using Context) + extends Message(PureExpressionInStatementPositionID) { + def kind = MessageKind.PotentialIssue + def msg(using Context) = "A pure expression does nothing in statement position; you may be omitting necessary parentheses" + def explain(using Context) = + i"""The pure expression $stat doesn't have any side effect and its result is not assigned elsewhere. + |It can be removed without changing the semantics of the program. This may indicate an error.""" +} + +class TraitCompanionWithMutableStatic()(using Context) + extends SyntaxMsg(TraitCompanionWithMutableStaticID) { + def msg(using Context) = i"Companion of traits cannot define mutable @static fields" + def explain(using Context) = "" +} + +class LazyStaticField()(using Context) + extends SyntaxMsg(LazyStaticFieldID) { + def msg(using Context) = i"Lazy @static fields are not supported" + def explain(using Context) = "" +} + +class StaticOverridingNonStaticMembers()(using Context) + extends SyntaxMsg(StaticOverridingNonStaticMembersID) { + def msg(using Context) = i"${hl("@static")} members cannot override or implement non-static ones" + def explain(using Context) = "" +} + +class OverloadInRefinement(rsym: Symbol)(using Context) + extends DeclarationMsg(OverloadInRefinementID) { + def msg(using Context) = "Refinements cannot introduce overloaded definitions" + def explain(using Context) = + i"""The refinement `$rsym` introduces an overloaded definition. + |Refinements cannot contain overloaded definitions.""" +} + +class NoMatchingOverload(val alternatives: List[SingleDenotation], pt: Type)(using Context) + extends TypeMsg(NoMatchingOverloadID) { + def msg(using Context) = + i"""None of the ${err.overloadedAltsStr(alternatives)} + |match ${err.expectedTypeStr(pt)}""" + def explain(using Context) = "" +} +class StableIdentPattern(tree: untpd.Tree, pt: Type)(using Context) + extends TypeMsg(StableIdentPatternID) { + def msg(using Context) = + i"""Stable identifier required, but $tree found""" + def explain(using Context) = "" +} + +class IllegalSuperAccessor(base: Symbol, memberName: Name, targetName: Name, + acc: Symbol, accTp: Type, + other: Symbol, otherTp: Type)(using Context) extends DeclarationMsg(IllegalSuperAccessorID) { + def msg(using Context) = { + // The mixin containing a super-call that requires a super-accessor + val accMixin = acc.owner + // The class or trait that the super-accessor should resolve too in `base` + val otherMixin = other.owner + // The super-call in `accMixin` + val superCall = hl(i"super.$memberName") + // The super-call that the super-accesors in `base` forwards to + val resolvedSuperCall = hl(i"super[${otherMixin.name}].$memberName") + // The super-call that we would have called if `super` in traits behaved like it + // does in classes, i.e. followed the linearization of the trait itself. + val staticSuperCall = { + val staticSuper = accMixin.asClass.info.parents.reverse + .find(_.nonPrivateMember(memberName) + .matchingDenotation(accMixin.thisType, acc.info, targetName).exists) + val staticSuperName = staticSuper match { + case Some(parent) => + parent.classSymbol.name.show + case None => // Might be reachable under separate compilation + "SomeParent" + } + hl(i"super[$staticSuperName].$memberName") } - } - - class MissingCompanionForStatic(member: Symbol)(using Context) - extends SyntaxMsg(MissingCompanionForStaticID) { - def msg = em"${member.owner} does not have a companion class" - def explain = - em"An object that contains ${hl("@static")} members must have a companion class." - } - - class PolymorphicMethodMissingTypeInParent(rsym: Symbol, parentSym: Symbol)(using Context) - extends SyntaxMsg(PolymorphicMethodMissingTypeInParentID) { - def msg = em"Polymorphic refinement $rsym without matching type in parent $parentSym is no longer allowed" - def explain = - em"""Polymorphic $rsym is not allowed in the structural refinement of $parentSym because - |$rsym does not override any method in $parentSym. Structural refinement does not allow for - |polymorphic methods.""" - } + i"""$base cannot be defined due to a conflict between its parents when + |implementing a super-accessor for $memberName in $accMixin: + | + |1. One of its parent (${accMixin.name}) contains a call $superCall in its body, + | and when a super-call in a trait is written without an explicit parent + | listed in brackets, it is implemented by a generated super-accessor in + | the class that extends this trait based on the linearization order of + | the class. + |2. Because ${otherMixin.name} comes before ${accMixin.name} in the linearization + | order of ${base.name}, and because ${otherMixin.name} overrides $memberName, + | the super-accessor in ${base.name} is implemented as a call to + | $resolvedSuperCall. + |3. However, + | ${otherTp.widenExpr} (the type of $resolvedSuperCall in ${base.name}) + | is not a subtype of + | ${accTp.widenExpr} (the type of $memberName in $accMixin). + | Hence, the super-accessor that needs to be generated in ${base.name} + | is illegal. + | + |Here are two possible ways to resolve this: + | + |1. Change the linearization order of ${base.name} such that + | ${accMixin.name} comes before ${otherMixin.name}. + |2. Alternatively, replace $superCall in the body of $accMixin by a + | super-call to a specific parent, e.g. $staticSuperCall + |""" + } + def explain(using Context) = "" +} + +class TraitParameterUsedAsParentPrefix(cls: Symbol)(using Context) + extends DeclarationMsg(TraitParameterUsedAsParentPrefixID) { + def msg(using Context) = + s"${cls.show} cannot extend from a parent that is derived via its own parameters" + def explain(using Context) = + i""" + |The parent class/trait that ${cls.show} extends from is obtained from + |the parameter of ${cls.show}. This is disallowed in order to prevent + |outer-related Null Pointer Exceptions in Scala. + | + |In order to fix this issue consider directly extending from the parent rather + |than obtaining it from the parameters of ${cls.show}. + |""" +} + +class UnknownNamedEnclosingClassOrObject(name: TypeName)(using Context) + extends ReferenceMsg(UnknownNamedEnclosingClassOrObjectID) { + def msg(using Context) = + i"""no enclosing class or object is named '${hl(name.show)}'""" + def explain(using Context) = + i""" + |The class or object named '${hl(name.show)}' was used as a visibility + |modifier, but could not be resolved. Make sure that + |'${hl(name.show)}' is not misspelled and has been imported into the + |current scope. + """ + } + +class IllegalCyclicTypeReference(sym: Symbol, where: String, lastChecked: Type)(using Context) + extends CyclicMsg(IllegalCyclicTypeReferenceID) { + def msg(using Context) = + val lastCheckedStr = + try lastChecked.show + catch case ex: CyclicReference => "..." + i"illegal cyclic type reference: ${where} ${hl(lastCheckedStr)} of $sym refers back to the type itself" + def explain(using Context) = "" +} + +class ErasedTypesCanOnlyBeFunctionTypes()(using Context) + extends SyntaxMsg(ErasedTypesCanOnlyBeFunctionTypesID) { + def msg(using Context) = "Types with erased keyword can only be function types `(erased ...) => ...`" + def explain(using Context) = "" +} + +class CaseClassMissingNonImplicitParamList(cdef: untpd.TypeDef)(using Context) + extends SyntaxMsg(CaseClassMissingNonImplicitParamListID) { + def msg(using Context) = + i"""|A ${hl("case class")} must have at least one leading non-implicit parameter list""" + + def explain(using Context) = + i"""|${cdef.name} must have at least one leading non-implicit parameter list, + | if you're aiming to have a case class parametrized only by implicit ones, you should + | add an explicit ${hl("()")} as the first parameter list to ${cdef.name}.""" +} + +class EnumerationsShouldNotBeEmpty(cdef: untpd.TypeDef)(using Context) + extends SyntaxMsg(EnumerationsShouldNotBeEmptyID) { + def msg(using Context) = "Enumerations must contain at least one case" + + def explain(using Context) = + i"""|Enumeration ${cdef.name} must contain at least one case + |Example Usage: + | ${hl("enum")} ${cdef.name} { + | ${hl("case")} Option1, Option2 + | } + |""" +} + +class TypedCaseDoesNotExplicitlyExtendTypedEnum(enumDef: Symbol, caseDef: untpd.TypeDef)(using Context) + extends SyntaxMsg(TypedCaseDoesNotExplicitlyExtendTypedEnumID) { + def msg(using Context) = i"explicit extends clause needed because both enum case and enum class have type parameters" + + def explain(using Context) = + i"""Enumerations where the enum class as well as the enum case have type parameters need + |an explicit extends. + |for example: + | ${hl("enum")} ${enumDef.name}[T] { + | ${hl("case")} ${caseDef.name}[U](u: U) ${hl("extends")} ${enumDef.name}[U] + | } + |""" +} + +class IllegalRedefinitionOfStandardKind(kindType: String, name: Name)(using Context) + extends SyntaxMsg(IllegalRedefinitionOfStandardKindID) { + def msg(using Context) = i"illegal redefinition of standard $kindType $name" + def explain(using Context) = + i"""| "$name" is a standard Scala core `$kindType` + | Please choose a different name to avoid conflicts + |""" +} + +class NoExtensionMethodAllowed(mdef: untpd.DefDef)(using Context) + extends SyntaxMsg(NoExtensionMethodAllowedID) { + def msg(using Context) = i"No extension method allowed here, since collective parameters are given" + def explain(using Context) = + i"""|Extension method: + | `${mdef}` + |is defined inside an extension clause which has collective parameters. + |""" +} - class ParamsNoInline(owner: Symbol)(using Context) - extends SyntaxMsg(ParamsNoInlineID) { - def msg = em"""${hl("inline")} modifier can only be used for parameters of inline methods""" - def explain = "" - } +class ExtensionMethodCannotHaveTypeParams(mdef: untpd.DefDef)(using Context) + extends SyntaxMsg(ExtensionMethodCannotHaveTypeParamsID) { + def msg(using Context) = i"Extension method cannot have type parameters since some were already given previously" - class JavaSymbolIsNotAValue(symbol: Symbol)(using Context) extends TypeMsg(JavaSymbolIsNotAValueID) { - def msg = { - val kind = - if (symbol is Package) em"$symbol" - else em"Java defined ${hl("class " + symbol.name)}" + def explain(using Context) = + i"""|Extension method: + | `${mdef}` + |has type parameters `[${mdef.leadingTypeParams.map(_.show).mkString(",")}]`, while the extension clause has + |it's own type parameters. Please consider moving these to the extension clause's type parameter list. + |""" +} + +class ExtensionCanOnlyHaveDefs(mdef: untpd.Tree)(using Context) + extends SyntaxMsg(ExtensionCanOnlyHaveDefsID) { + def msg(using Context) = i"Only methods allowed here, since collective parameters are given" + def explain(using Context) = + i"""Extension clauses can only have `def`s + | `${mdef.show}` is not a valid expression here. + |""" +} + +class UnexpectedPatternForSummonFrom(tree: Tree[_])(using Context) + extends SyntaxMsg(UnexpectedPatternForSummonFromID) { + def msg(using Context) = i"Unexpected pattern for summonFrom. Expected ${hl("`x: T`")} or ${hl("`_`")}" + def explain(using Context) = + i"""|The pattern "${tree.show}" provided in the ${hl("case")} expression of the ${hl("summonFrom")}, + | needs to be of the form ${hl("`x: T`")} or ${hl("`_`")}. + | + | Example usage: + | inline def a = summonFrom { + | case x: T => ??? + | } + | + | or + | inline def a = summonFrom { + | case _ => ??? + | } + |""" +} + +class AnonymousInstanceCannotBeEmpty(impl: untpd.Template)(using Context) + extends SyntaxMsg(AnonymousInstanceCannotBeEmptyID) { + def msg(using Context) = i"anonymous instance must implement a type or have at least one extension method" + def explain(using Context) = + i"""|Anonymous instances cannot be defined with an empty body. The block + |`${impl.show}` should either contain an implemented type or at least one extension method. + |""" +} + +class ModifierNotAllowedForDefinition(flag: Flag)(using Context) + extends SyntaxMsg(ModifierNotAllowedForDefinitionID) { + def msg(using Context) = i"Modifier ${hl(flag.flagsString)} is not allowed for this definition" + def explain(using Context) = "" +} + +class RedundantModifier(flag: Flag)(using Context) + extends SyntaxMsg(RedundantModifierID) { + def msg(using Context) = i"Modifier ${hl(flag.flagsString)} is redundant for this definition" + def explain(using Context) = "" +} + +class InvalidReferenceInImplicitNotFoundAnnotation(typeVar: String, owner: String)(using Context) + extends ReferenceMsg(InvalidReferenceInImplicitNotFoundAnnotationID) { + def msg(using Context) = i"""|Invalid reference to a type variable ${hl(typeVar)} found in the annotation argument. + |The variable does not occur as a parameter in the scope of ${hl(owner)}. + |""" + def explain(using Context) = "" +} + +class CaseClassInInlinedCode(tree: tpd.Tree)(using Context) + extends SyntaxMsg(CaseClassInInlinedCodeID) { + + def defKind = if tree.symbol.is(Module) then "object" else "class" + def msg(using Context) = s"Case $defKind definitions are not allowed in inline methods or quoted code. Use a normal $defKind instead." + def explain(using Context) = + i"""Case class/object definitions generate a considerable footprint in code size. + |Inlining such definition would multiply this footprint for each call site. + |""" +} + +class ImplicitSearchTooLargeWarning(limit: Int, openSearchPairs: List[(Candidate, Type)])(using Context) + extends TypeMsg(ImplicitSearchTooLargeID): + override def showAlways = true + def showQuery(query: (Candidate, Type))(using Context): String = + i" ${query._1.ref.symbol.showLocated} for ${query._2}}" + def msg(using Context) = + i"""Implicit search problem too large. + |an implicit search was terminated with failure after trying $limit expressions. + |The root candidate for the search was: + | + |${showQuery(openSearchPairs.last)} + | + |You can change the behavior by setting the `-Ximplicit-search-limit` value. + |Smaller values cause the search to fail faster. + |Larger values might make a very large search problem succeed. + |""" + def explain(using Context) = + i"""The overflow happened with the following lists of tried expressions and target types, + |starting with the root query: + | + |${openSearchPairs.reverse.map(showQuery)}%\n% + """ + +class TargetNameOnTopLevelClass(symbol: Symbol)(using Context) +extends SyntaxMsg(TargetNameOnTopLevelClassID): + def msg(using Context) = i"${hl("@targetName")} annotation not allowed on top-level $symbol" + def explain(using Context) = + val annot = symbol.getAnnotation(defn.TargetNameAnnot).get + i"""The @targetName annotation may be applied to a top-level ${hl("val")} or ${hl("def")}, but not + |a top-level ${hl("class")}, ${hl("trait")}, or ${hl("object")}. + | + |This restriction is due to the naming convention of Java classfiles, whose filenames + |are based on the name of the class defined within. If @targetName were permitted + |here, the name of the classfile would be based on the target name, and the compiler + |could not associate that classfile with the Scala-visible defined name of the class. + | + |If your use case requires @targetName, consider wrapping $symbol in an ${hl("object")} + |(and possibly exporting it), as in the following example: + | + |${hl("object Wrapper:")} + | $annot $symbol { ... } + | + |${hl("export")} Wrapper.${symbol.name} ${hl("// optional")}""" + +class NotClassType(tp: Type)(using Context) +extends TypeMsg(NotClassTypeID), ShowMatchTrace(tp): + def msg(using Context) = i"$tp is not a class type" + def explain(using Context) = "" + +class MissingImplicitArgument( + arg: tpd.Tree, + pt: Type, + where: String, + paramSymWithMethodCallTree: Option[(Symbol, tpd.Tree)] = None, + ignoredInstanceNormalImport: -> Option[SearchSuccess] + )(using Context) extends TypeMsg(MissingImplicitArgumentID), ShowMatchTrace(pt): + + arg.tpe match + case ambi: AmbiguousImplicits => withoutDisambiguation() + case _ => + + def msg(using Context): String = + + def formatMsg(shortForm: String)(headline: String = shortForm) = arg match + case arg: Trees.SearchFailureIdent[?] => + arg.tpe match + case _: NoMatchingImplicits => headline + case tpe: SearchFailureType => + i"$headline. ${tpe.explanation}" + case _ => headline + case _ => + arg.tpe match + case tpe: SearchFailureType => + val original = arg match + case Inlined(call, _, _) => call + case _ => arg + i"""$headline. + |I found: + | + | ${original.show.replace("\n", "\n ")} + | + |But ${tpe.explanation}.""" + case _ => headline + + /** Format `raw` implicitNotFound or implicitAmbiguous argument, replacing + * all occurrences of `${X}` where `X` is in `paramNames` with the + * corresponding shown type in `args`. + */ + def userDefinedErrorString(raw: String, paramNames: List[String], args: List[Type]): String = { + def translate(name: String): Option[String] = { + val idx = paramNames.indexOf(name) + if (idx >= 0) Some(i"${args(idx)}") else None + } - s"$kind is not a value" + """\$\{\s*([^}\s]+)\s*\}""".r.replaceAllIn(raw, (_: Regex.Match) match { + case Regex.Groups(v) => quoteReplacement(translate(v).getOrElse("")).nn + }) } - def explain = "" - } - class DoubleDefinition(decl: Symbol, previousDecl: Symbol, base: Symbol)(using Context) extends NamingMsg(DoubleDefinitionID) { - def msg = { - def nameAnd = if (decl.name != previousDecl.name) " name and" else "" - def erasedType = if ctx.erasedTypes then i" ${decl.info}" else "" - def details(using Context): String = - if (decl.isRealMethod && previousDecl.isRealMethod) { - import Signature.MatchDegree._ - - // compare the signatures when both symbols represent methods - decl.signature.matchDegree(previousDecl.signature) match { - case NoMatch => - // If the signatures don't match at all at the current phase, then - // they might match after erasure. - if ctx.phase.id <= elimErasedValueTypePhase.id then - atPhase(elimErasedValueTypePhase.next)(details) - else - "" // shouldn't be reachable - case ParamMatch => - "have matching parameter types." - case MethodNotAMethodMatch => - "neither has parameters." - case FullMatch => - val hint = - if !decl.hasAnnotation(defn.TargetNameAnnot) - && !previousDecl.hasAnnotation(defn.TargetNameAnnot) - then - i""" - | - |Consider adding a @targetName annotation to one of the conflicting definitions - |for disambiguation.""" - else "" - i"have the same$nameAnd type$erasedType after erasure.$hint" - } - } - else "" - def symLocation(sym: Symbol) = { - val lineDesc = - if (sym.span.exists && sym.span != sym.owner.span) - s" at line ${sym.srcPos.line + 1}" - else "" - i"in ${sym.owner}${lineDesc}" + /** Extract a user defined error message from a symbol `sym` + * with an annotation matching the given class symbol `cls`. + */ + def userDefinedMsg(sym: Symbol, cls: Symbol) = for { + ann <- sym.getAnnotation(cls) + msg <- ann.argumentConstantString(0) + } yield msg + + def location(preposition: String) = if (where.isEmpty) "" else s" $preposition $where" + + def defaultAmbiguousImplicitMsg(ambi: AmbiguousImplicits) = + s"Ambiguous given instances: ${ambi.explanation}${location("of")}" + + def defaultImplicitNotFoundMessage = + i"No given instance of type $pt was found${location("for")}" + + /** Construct a custom error message given an ambiguous implicit + * candidate `alt` and a user defined message `raw`. + */ + def userDefinedAmbiguousImplicitMsg(alt: SearchSuccess, raw: String) = { + val params = alt.ref.underlying match { + case p: PolyType => p.paramNames.map(_.toString) + case _ => Nil } - val clashDescription = - if (decl.owner eq previousDecl.owner) - "Double definition" - else if ((decl.owner eq base) || (previousDecl eq base)) - "Name clash between defined and inherited member" - else - "Name clash between inherited members" - - atPhase(typerPhase) { - em"""$clashDescription: - |${previousDecl.showDcl} ${symLocation(previousDecl)} and - |${decl.showDcl} ${symLocation(decl)} - |""" - } + details + def resolveTypes(targs: List[tpd.Tree])(using Context) = + targs.map(a => Inferencing.fullyDefinedType(a.tpe, "type argument", a.srcPos)) + + // We can extract type arguments from: + // - a function call: + // @implicitAmbiguous("msg A=${A}") + // implicit def f[A](): String = ... + // implicitly[String] // found: f[Any]() + // + // - an eta-expanded function: + // @implicitAmbiguous("msg A=${A}") + // implicit def f[A](x: Int): String = ... + // implicitly[Int => String] // found: x => f[Any](x) + + val call = tpd.closureBody(alt.tree) // the tree itself if not a closure + val targs = tpd.typeArgss(call).flatten + val args = resolveTypes(targs)(using ctx.fresh.setTyperState(alt.tstate)) + userDefinedErrorString(raw, params, args) } - def explain = "" - } - - class ImportRenamedTwice(ident: untpd.Ident)(using Context) extends SyntaxMsg(ImportRenamedTwiceID) { - def msg = s"${ident.show} is renamed twice on the same import line." - def explain = "" - } - - class TypeTestAlwaysDiverges(scrutTp: Type, testTp: Type)(using Context) extends SyntaxMsg(TypeTestAlwaysDivergesID) { - def msg = - s"This type test will never return a result since the scrutinee type ${scrutTp.show} does not contain any value." - def explain = "" - } - - // Relative of CyclicReferenceInvolvingImplicit and RecursiveValueNeedsResultType - class TermMemberNeedsResultTypeForImplicitSearch(cycleSym: Symbol)(using Context) - extends CyclicMsg(TermMemberNeedsNeedsResultTypeForImplicitSearchID) { - def msg = em"""$cycleSym needs result type because its right-hand side attempts implicit search""" - def explain = - em"""|The right hand-side of $cycleSym's definition requires an implicit search at the highlighted position. - |To avoid this error, give `$cycleSym` an explicit type. - |""".stripMargin - } - - class ClassCannotExtendEnum(cls: Symbol, parent: Symbol)(using Context) extends SyntaxMsg(ClassCannotExtendEnumID) { - def msg = em"""$cls in ${cls.owner} extends enum ${parent.name}, but extending enums is prohibited.""" - def explain = "" - } - - class NotAnExtractor(tree: untpd.Tree)(using Context) extends SyntaxMsg(NotAnExtractorID) { - def msg = em"$tree cannot be used as an extractor in a pattern because it lacks an unapply or unapplySeq method" - def explain = - em"""|An ${hl("unapply")} method should be defined in an ${hl("object")} as follow: - | - If it is just a test, return a ${hl("Boolean")}. For example ${hl("case even()")} - | - If it returns a single sub-value of type T, return an ${hl("Option[T]")} - | - If it returns several sub-values T1,...,Tn, group them in an optional tuple ${hl("Option[(T1,...,Tn)]")} - | - |Sometimes, the number of sub-values isn't fixed and we would like to return a sequence. - |For this reason, you can also define patterns through ${hl("unapplySeq")} which returns ${hl("Option[Seq[T]]")}. - |This mechanism is used for instance in pattern ${hl("case List(x1, ..., xn)")}""".stripMargin - } - - class MemberWithSameNameAsStatic()(using Context) - extends SyntaxMsg(MemberWithSameNameAsStaticID) { - def msg = em"Companion classes cannot define members with same name as a ${hl("@static")} member" - def explain = "" - } - - class PureExpressionInStatementPosition(stat: untpd.Tree, val exprOwner: Symbol)(using Context) - extends Message(PureExpressionInStatementPositionID) { - def kind = MessageKind.PotentialIssue - def msg = "A pure expression does nothing in statement position; you may be omitting necessary parentheses" - def explain = - em"""The pure expression $stat doesn't have any side effect and its result is not assigned elsewhere. - |It can be removed without changing the semantics of the program. This may indicate an error.""".stripMargin - } - class TraitCompanionWithMutableStatic()(using Context) - extends SyntaxMsg(TraitCompanionWithMutableStaticID) { - def msg = em"Companion of traits cannot define mutable @static fields" - def explain = "" - } - - class LazyStaticField()(using Context) - extends SyntaxMsg(LazyStaticFieldID) { - def msg = em"Lazy @static fields are not supported" - def explain = "" - } - - class StaticOverridingNonStaticMembers()(using Context) - extends SyntaxMsg(StaticOverridingNonStaticMembersID) { - def msg = em"${hl("@static")} members cannot override or implement non-static ones" - def explain = "" - } - - class OverloadInRefinement(rsym: Symbol)(using Context) - extends DeclarationMsg(OverloadInRefinementID) { - def msg = "Refinements cannot introduce overloaded definitions" - def explain = - em"""The refinement `$rsym` introduces an overloaded definition. - |Refinements cannot contain overloaded definitions.""".stripMargin - } - - class NoMatchingOverload(val alternatives: List[SingleDenotation], pt: Type)(using Context) - extends TypeMsg(NoMatchingOverloadID) { - def msg = - em"""None of the ${err.overloadedAltsStr(alternatives)} - |match ${err.expectedTypeStr(pt)}""" - def explain = "" - } - class StableIdentPattern(tree: untpd.Tree, pt: Type)(using Context) - extends TypeMsg(StableIdentPatternID) { - def msg = - em"""Stable identifier required, but $tree found""" - def explain = "" - } + /** @param rawMsg Message template with variables, e.g. "Variable A is ${A}" + * @param sym Symbol of the annotated type or of the method whose parameter was annotated + * @param substituteType Function substituting specific types for abstract types associated with variables, e.g A -> Int + */ + def formatAnnotationMessage(rawMsg: String, sym: Symbol, substituteType: Type => Type): String = { + val substitutableTypesSymbols = substitutableTypeSymbolsInScope(sym) + + userDefinedErrorString( + rawMsg, + paramNames = substitutableTypesSymbols.map(_.name.unexpandedName.toString), + args = substitutableTypesSymbols.map(_.typeRef).map(substituteType) + ) + } - class IllegalSuperAccessor(base: Symbol, memberName: Name, targetName: Name, - acc: Symbol, accTp: Type, - other: Symbol, otherTp: Type)(using Context) extends DeclarationMsg(IllegalSuperAccessorID) { - def msg = { - // The mixin containing a super-call that requires a super-accessor - val accMixin = acc.owner - // The class or trait that the super-accessor should resolve too in `base` - val otherMixin = other.owner - // The super-call in `accMixin` - val superCall = hl(i"super.$memberName") - // The super-call that the super-accesors in `base` forwards to - val resolvedSuperCall = hl(i"super[${otherMixin.name}].$memberName") - // The super-call that we would have called if `super` in traits behaved like it - // does in classes, i.e. followed the linearization of the trait itself. - val staticSuperCall = { - val staticSuper = accMixin.asClass.info.parents.reverse - .find(_.nonPrivateMember(memberName) - .matchingDenotation(accMixin.thisType, acc.info, targetName).exists) - val staticSuperName = staticSuper match { - case Some(parent) => - parent.classSymbol.name.show - case None => // Might be reachable under separate compilation - "SomeParent" - } - hl(i"super[$staticSuperName].$memberName") + /** Extracting the message from a method parameter, e.g. in + * + * trait Foo + * + * def foo(implicit @annotation.implicitNotFound("Foo is missing") foo: Foo): Any = ??? + */ + def userDefinedImplicitNotFoundParamMessage: Option[String] = paramSymWithMethodCallTree.flatMap { (sym, applTree) => + userDefinedMsg(sym, defn.ImplicitNotFoundAnnot).map { rawMsg => + val fn = tpd.funPart(applTree) + val targs = tpd.typeArgss(applTree).flatten + val methodOwner = fn.symbol.owner + val methodOwnerType = tpd.qualifier(fn).tpe + val methodTypeParams = fn.symbol.paramSymss.flatten.filter(_.isType) + val methodTypeArgs = targs.map(_.tpe) + val substituteType = (_: Type).asSeenFrom(methodOwnerType, methodOwner).subst(methodTypeParams, methodTypeArgs) + formatAnnotationMessage(rawMsg, sym.owner, substituteType) } - ex"""$base cannot be defined due to a conflict between its parents when - |implementing a super-accessor for $memberName in $accMixin: - | - |1. One of its parent (${accMixin.name}) contains a call $superCall in its body, - | and when a super-call in a trait is written without an explicit parent - | listed in brackets, it is implemented by a generated super-accessor in - | the class that extends this trait based on the linearization order of - | the class. - |2. Because ${otherMixin.name} comes before ${accMixin.name} in the linearization - | order of ${base.name}, and because ${otherMixin.name} overrides $memberName, - | the super-accessor in ${base.name} is implemented as a call to - | $resolvedSuperCall. - |3. However, - | ${otherTp.widenExpr} (the type of $resolvedSuperCall in ${base.name}) - | is not a subtype of - | ${accTp.widenExpr} (the type of $memberName in $accMixin). - | Hence, the super-accessor that needs to be generated in ${base.name} - | is illegal. - | - |Here are two possible ways to resolve this: - | - |1. Change the linearization order of ${base.name} such that - | ${accMixin.name} comes before ${otherMixin.name}. - |2. Alternatively, replace $superCall in the body of $accMixin by a - | super-call to a specific parent, e.g. $staticSuperCall - |""".stripMargin } - def explain = "" - } - class TraitParameterUsedAsParentPrefix(cls: Symbol)(using Context) - extends DeclarationMsg(TraitParameterUsedAsParentPrefixID) { - def msg = - s"${cls.show} cannot extend from a parent that is derived via its own parameters" - def explain = - ex""" - |The parent class/trait that ${cls.show} extends from is obtained from - |the parameter of ${cls.show}. This is disallowed in order to prevent - |outer-related Null Pointer Exceptions in Scala. - | - |In order to fix this issue consider directly extending from the parent rather - |than obtaining it from the parameters of ${cls.show}. - |""".stripMargin - } - - class UnknownNamedEnclosingClassOrObject(name: TypeName)(using Context) - extends ReferenceMsg(UnknownNamedEnclosingClassOrObjectID) { - def msg = - em"""no enclosing class or object is named '${hl(name.show)}'""" - def explain = - ex""" - |The class or object named '${hl(name.show)}' was used as a visibility - |modifier, but could not be resolved. Make sure that - |'${hl(name.show)}' is not misspelled and has been imported into the - |current scope. - """.stripMargin + /** Extracting the message from a type, e.g. in + * + * @annotation.implicitNotFound("Foo is missing") + * trait Foo + * + * def foo(implicit foo: Foo): Any = ??? + */ + def userDefinedImplicitNotFoundTypeMessage: Option[String] = + def recur(tp: Type): Option[String] = tp match + case tp: TypeRef => + val sym = tp.symbol + userDefinedImplicitNotFoundTypeMessageFor(sym).orElse(recur(tp.info)) + case tp: ClassInfo => + tp.baseClasses.iterator + .map(userDefinedImplicitNotFoundTypeMessageFor) + .find(_.isDefined).flatten + case tp: TypeProxy => + recur(tp.superType) + case tp: AndType => + recur(tp.tp1).orElse(recur(tp.tp2)) + case _ => + None + recur(pt) + + def userDefinedImplicitNotFoundTypeMessageFor(sym: Symbol): Option[String] = + for + rawMsg <- userDefinedMsg(sym, defn.ImplicitNotFoundAnnot) + if Feature.migrateTo3 || sym != defn.Function1 + // Don't inherit "No implicit view available..." message if subtypes of Function1 are not treated as implicit conversions anymore + yield + val substituteType = (_: Type).asSeenFrom(pt, sym) + formatAnnotationMessage(rawMsg, sym, substituteType) + + object AmbiguousImplicitMsg { + def unapply(search: SearchSuccess): Option[String] = + userDefinedMsg(search.ref.symbol, defn.ImplicitAmbiguousAnnot) } - class IllegalCyclicTypeReference(sym: Symbol, where: String, lastChecked: Type)(using Context) - extends CyclicMsg(IllegalCyclicTypeReferenceID) { - def msg = - val lastCheckedStr = - try lastChecked.show - catch case ex: CyclicReference => "..." - i"illegal cyclic type reference: ${where} ${hl(lastCheckedStr)} of $sym refers back to the type itself" - def explain = "" - } - - class ErasedTypesCanOnlyBeFunctionTypes()(using Context) - extends SyntaxMsg(ErasedTypesCanOnlyBeFunctionTypesID) { - def msg = "Types with erased keyword can only be function types `(erased ...) => ...`" - def explain = "" - } - - class CaseClassMissingNonImplicitParamList(cdef: untpd.TypeDef)(using Context) - extends SyntaxMsg(CaseClassMissingNonImplicitParamListID) { - def msg = - em"""|A ${hl("case class")} must have at least one leading non-implicit parameter list""" - - def explain = - em"""|${cdef.name} must have at least one leading non-implicit parameter list, - | if you're aiming to have a case class parametrized only by implicit ones, you should - | add an explicit ${hl("()")} as the first parameter list to ${cdef.name}.""".stripMargin - } - - class EnumerationsShouldNotBeEmpty(cdef: untpd.TypeDef)(using Context) - extends SyntaxMsg(EnumerationsShouldNotBeEmptyID) { - def msg = "Enumerations must contain at least one case" - - def explain = - em"""|Enumeration ${cdef.name} must contain at least one case - |Example Usage: - | ${hl("enum")} ${cdef.name} { - | ${hl("case")} Option1, Option2 - | } - |""".stripMargin - } - - class TypedCaseDoesNotExplicitlyExtendTypedEnum(enumDef: Symbol, caseDef: untpd.TypeDef)(using Context) - extends SyntaxMsg(TypedCaseDoesNotExplicitlyExtendTypedEnumID) { - def msg = i"explicit extends clause needed because both enum case and enum class have type parameters" - - def explain = - em"""Enumerations where the enum class as well as the enum case have type parameters need - |an explicit extends. - |for example: - | ${hl("enum")} ${enumDef.name}[T] { - | ${hl("case")} ${caseDef.name}[U](u: U) ${hl("extends")} ${enumDef.name}[U] - | } - |""".stripMargin - } - - class IllegalRedefinitionOfStandardKind(kindType: String, name: Name)(using Context) - extends SyntaxMsg(IllegalRedefinitionOfStandardKindID) { - def msg = em"illegal redefinition of standard $kindType $name" - def explain = - em"""| "$name" is a standard Scala core `$kindType` - | Please choose a different name to avoid conflicts - |""".stripMargin - } - - class NoExtensionMethodAllowed(mdef: untpd.DefDef)(using Context) - extends SyntaxMsg(NoExtensionMethodAllowedID) { - def msg = em"No extension method allowed here, since collective parameters are given" - def explain = - em"""|Extension method: - | `${mdef}` - |is defined inside an extension clause which has collective parameters. - |""".stripMargin - } - - class ExtensionMethodCannotHaveTypeParams(mdef: untpd.DefDef)(using Context) - extends SyntaxMsg(ExtensionMethodCannotHaveTypeParamsID) { - def msg = i"Extension method cannot have type parameters since some were already given previously" - - def explain = - em"""|Extension method: - | `${mdef}` - |has type parameters `[${mdef.leadingTypeParams.map(_.show).mkString(",")}]`, while the extension clause has - |it's own type parameters. Please consider moving these to the extension clause's type parameter list. - |""".stripMargin - } - - class ExtensionCanOnlyHaveDefs(mdef: untpd.Tree)(using Context) - extends SyntaxMsg(ExtensionCanOnlyHaveDefsID) { - def msg = em"Only methods allowed here, since collective parameters are given" - def explain = - em"""Extension clauses can only have `def`s - | `${mdef.show}` is not a valid expression here. - |""".stripMargin - } - - class UnexpectedPatternForSummonFrom(tree: Tree[_])(using Context) - extends SyntaxMsg(UnexpectedPatternForSummonFromID) { - def msg = em"Unexpected pattern for summonFrom. Expected ${hl("`x: T`")} or ${hl("`_`")}" - def explain = - em"""|The pattern "${tree.show}" provided in the ${hl("case")} expression of the ${hl("summonFrom")}, - | needs to be of the form ${hl("`x: T`")} or ${hl("`_`")}. - | - | Example usage: - | inline def a = summonFrom { - | case x: T => ??? - | } - | - | or - | inline def a = summonFrom { - | case _ => ??? - | } - |""".stripMargin - } - - class AnonymousInstanceCannotBeEmpty(impl: untpd.Template)(using Context) - extends SyntaxMsg(AnonymousInstanceCannotBeEmptyID) { - def msg = i"anonymous instance must implement a type or have at least one extension method" - def explain = - em"""|Anonymous instances cannot be defined with an empty body. The block - |`${impl.show}` should either contain an implemented type or at least one extension method. - |""".stripMargin - } - - class ModifierNotAllowedForDefinition(flag: Flag)(using Context) - extends SyntaxMsg(ModifierNotAllowedForDefinitionID) { - def msg = em"Modifier ${hl(flag.flagsString)} is not allowed for this definition" - def explain = "" - } - - class RedundantModifier(flag: Flag)(using Context) - extends SyntaxMsg(RedundantModifierID) { - def msg = em"Modifier ${hl(flag.flagsString)} is redundant for this definition" - def explain = "" - } - - class InvalidReferenceInImplicitNotFoundAnnotation(typeVar: String, owner: String)(using Context) - extends ReferenceMsg(InvalidReferenceInImplicitNotFoundAnnotationID) { - def msg = em"""|Invalid reference to a type variable ${hl(typeVar)} found in the annotation argument. - |The variable does not occur as a parameter in the scope of ${hl(owner)}. - |""".stripMargin - def explain = "" - } - - class CaseClassInInlinedCode(tree: tpd.Tree)(using Context) - extends SyntaxMsg(CaseClassInInlinedCodeID) { - - def defKind = if tree.symbol.is(Module) then "object" else "class" - def msg = s"Case $defKind definitions are not allowed in inline methods or quoted code. Use a normal $defKind instead." - def explain = - em"""Case class/object definitions generate a considerable footprint in code size. - |Inlining such definition would multiply this footprint for each call site. - |""".stripMargin - } - - class ImplicitSearchTooLargeWarning(limit: Int, openSearchPairs: List[(Candidate, Type)])(using Context) - extends TypeMsg(ImplicitSearchTooLargeID): - override def showAlways = true - def showQuery(query: (Candidate, Type)): String = - i" ${query._1.ref.symbol.showLocated} for ${query._2}}" - def msg = - em"""Implicit search problem too large. - |an implicit search was terminated with failure after trying $limit expressions. - |The root candidate for the search was: - | - |${showQuery(openSearchPairs.last)} - | - |You can change the behavior by setting the `-Ximplicit-search-limit` value. - |Smaller values cause the search to fail faster. - |Larger values might make a very large search problem succeed. - |""" - def explain = - em"""The overflow happened with the following lists of tried expressions and target types, - |starting with the root query: - | - |${openSearchPairs.reverse.map(showQuery)}%\n% - """ - - class TargetNameOnTopLevelClass(symbol: Symbol)(using Context) - extends SyntaxMsg(TargetNameOnTopLevelClassID): - def msg = em"${hl("@targetName")} annotation not allowed on top-level $symbol" - def explain = - val annot = symbol.getAnnotation(defn.TargetNameAnnot).get - em"""The @targetName annotation may be applied to a top-level ${hl("val")} or ${hl("def")}, but not - |a top-level ${hl("class")}, ${hl("trait")}, or ${hl("object")}. - | - |This restriction is due to the naming convention of Java classfiles, whose filenames - |are based on the name of the class defined within. If @targetName were permitted - |here, the name of the classfile would be based on the target name, and the compiler - |could not associate that classfile with the Scala-visible defined name of the class. - | - |If your use case requires @targetName, consider wrapping $symbol in an ${hl("object")} - |(and possibly exporting it), as in the following example: - | - |${hl("object Wrapper:")} - | $annot $symbol { ... } - | - |${hl("export")} Wrapper.${symbol.name} ${hl("// optional")}""" + arg.tpe match + case ambi: AmbiguousImplicits => + (ambi.alt1, ambi.alt2) match + case (alt @ AmbiguousImplicitMsg(msg), _) => + userDefinedAmbiguousImplicitMsg(alt, msg) + case (_, alt @ AmbiguousImplicitMsg(msg)) => + userDefinedAmbiguousImplicitMsg(alt, msg) + case _ => + defaultAmbiguousImplicitMsg(ambi) + case ambi @ TooUnspecific(target) => + i"""No implicit search was attempted${location("for")} + |since the expected type $target is not specific enough""" + case _ => + val shortMessage = userDefinedImplicitNotFoundParamMessage + .orElse(userDefinedImplicitNotFoundTypeMessage) + .getOrElse(defaultImplicitNotFoundMessage) + formatMsg(shortMessage)() + end msg + + override def msgPostscript(using Context) = + arg.tpe match + case _: AmbiguousImplicits => + "" // show no disambiguation + case _: TooUnspecific => + super.msgPostscript // show just disambigutation and match type trace + case _ => + // show all available additional info + def hiddenImplicitNote(s: SearchSuccess) = + i"\n\nNote: ${s.ref.symbol.showLocated} was not considered because it was not imported with `import given`." + super.msgPostscript + ++ ignoredInstanceNormalImport.map(hiddenImplicitNote) + .getOrElse(ctx.typer.importSuggestionAddendum(pt)) + + def explain(using Context) = "" +end MissingImplicitArgument + +class CannotBeAccessed(tpe: NamedType, superAccess: Boolean)(using Context) +extends ReferenceMsg(CannotBeAccessedID): + def msg(using Context) = + val pre = tpe.prefix + val name = tpe.name + val alts = tpe.denot.alternatives.map(_.symbol).filter(_.exists) + val whatCanNot = alts match + case Nil => + i"$name cannot" + case sym :: Nil => + i"${if (sym.owner == pre.typeSymbol) sym.show else sym.showLocated} cannot" + case _ => + i"none of the overloaded alternatives named $name can" + val where = if (ctx.owner.exists) s" from ${ctx.owner.enclosingClass}" else "" + val whyNot = new StringBuffer + alts.foreach(_.isAccessibleFrom(pre, superAccess, whyNot)) + i"$whatCanNot be accessed as a member of $pre$where.$whyNot" + def explain(using Context) = "" - class NotClassType(tp: Type)(using Context) - extends TypeMsg(NotClassTypeID), ShowMatchTrace(tp): - def msg = ex"$tp is not a class type" - def explain = "" diff --git a/tests/pos-with-compiler-cc/dotc/reporting/trace.scala b/tests/pos-with-compiler-cc/dotc/reporting/trace.scala index 7c114b51ed21..8e8d3efb8b40 100644 --- a/tests/pos-with-compiler-cc/dotc/reporting/trace.scala +++ b/tests/pos-with-compiler-cc/dotc/reporting/trace.scala @@ -4,10 +4,11 @@ package reporting import scala.language.unsafeNulls -import core.Contexts._ -import config.Config -import config.Printers -import core.Mode +import core.*, Contexts.*, Decorators.* +import config.* +import printing.Formatting.* + +import scala.compiletime.* /** Exposes the {{{ trace("question") { op } }}} syntax. * @@ -51,9 +52,20 @@ trait TraceSyntax: else op inline def apply[T](inline question: String, inline printer: Printers.Printer, inline show: Boolean)(inline op: T)(using Context): T = - inline if isEnabled then - doTrace[T](question, printer, if show then showShowable(_) else alwaysToString)(op) - else op + apply(question, printer, { + val showOp: T => String = inline if show == true then + val showT = summonInline[Show[T]] + { + given Show[T] = showT + t => i"$t" + } + else + summonFrom { + case given Show[T] => t => i"$t" + case _ => alwaysToString + } + showOp + })(op) inline def apply[T](inline question: String, inline printer: Printers.Printer)(inline op: T)(using Context): T = apply[T](question, printer, false)(op) @@ -64,15 +76,11 @@ trait TraceSyntax: inline def apply[T](inline question: String)(inline op: T)(using Context): T = apply[T](question, false)(op) - private def showShowable(x: Any)(using Context) = x match - case x: printing.Showable => x.show - case _ => String.valueOf(x) - private val alwaysToString = (x: Any) => String.valueOf(x) private def doTrace[T](question: => String, printer: Printers.Printer = Printers.default, - showOp: T => String = alwaysToString) + showOp: T => String) (op: => T)(using Context): T = if ctx.mode.is(Mode.Printing) || !isForced && (printer eq Printers.noPrinter) then op else diff --git a/tests/pos-with-compiler-cc/dotc/sbt/ExtractDependencies.scala b/tests/pos-with-compiler-cc/dotc/sbt/ExtractDependencies.scala index a554c8e5066f..c3e8f8a2c8b7 100644 --- a/tests/pos-with-compiler-cc/dotc/sbt/ExtractDependencies.scala +++ b/tests/pos-with-compiler-cc/dotc/sbt/ExtractDependencies.scala @@ -190,7 +190,7 @@ object ExtractDependencies { /** Report an internal error in incremental compilation. */ def internalError(msg: -> String, pos: SrcPos = NoSourcePosition)(using Context): Unit = - report.error(s"Internal error in the incremental compiler while compiling ${ctx.compilationUnit.source}: $msg", pos) + report.error(em"Internal error in the incremental compiler while compiling ${ctx.compilationUnit.source}: $msg", pos) } private case class ClassDependency(from: Symbol, to: Symbol, context: DependencyContext) diff --git a/tests/pos-with-compiler-cc/dotc/semanticdb/SemanticSymbolBuilder.scala b/tests/pos-with-compiler-cc/dotc/semanticdb/SemanticSymbolBuilder.scala index c825032373f8..c7b0dfd437db 100644 --- a/tests/pos-with-compiler-cc/dotc/semanticdb/SemanticSymbolBuilder.scala +++ b/tests/pos-with-compiler-cc/dotc/semanticdb/SemanticSymbolBuilder.scala @@ -74,7 +74,9 @@ class SemanticSymbolBuilder: def addOwner(owner: Symbol): Unit = if !owner.isRoot then addSymName(b, owner) - def addOverloadIdx(sym: Symbol): Unit = + def addOverloadIdx(initSym: Symbol): Unit = + // revert from the compiler-generated overload of the signature polymorphic method + val sym = initSym.originalSignaturePolymorphic.symbol.orElse(initSym) val decls = val decls0 = sym.owner.info.decls.lookupAll(sym.name) if sym.owner.isAllOf(JavaModule) then diff --git a/tests/pos-with-compiler-cc/dotc/transform/CheckReentrant.scala b/tests/pos-with-compiler-cc/dotc/transform/CheckReentrant.scala index 6b0a4c3e9737..b63773687f74 100644 --- a/tests/pos-with-compiler-cc/dotc/transform/CheckReentrant.scala +++ b/tests/pos-with-compiler-cc/dotc/transform/CheckReentrant.scala @@ -67,8 +67,8 @@ class CheckReentrant extends MiniPhase { if (sym.isTerm && !sym.isSetter && !isIgnored(sym)) if (sym.is(Mutable)) { report.error( - i"""possible data race involving globally reachable ${sym.showLocated}: ${sym.info} - | use -Ylog:checkReentrant+ to find out more about why the variable is reachable.""") + em"""possible data race involving globally reachable ${sym.showLocated}: ${sym.info} + | use -Ylog:checkReentrant+ to find out more about why the variable is reachable.""") shared += sym } else if (!sym.is(Method) || sym.isOneOf(Accessor | ParamAccessor)) diff --git a/tests/pos-with-compiler-cc/dotc/transform/CompleteJavaEnums.scala b/tests/pos-with-compiler-cc/dotc/transform/CompleteJavaEnums.scala index be454281bcbb..80b72075282f 100644 --- a/tests/pos-with-compiler-cc/dotc/transform/CompleteJavaEnums.scala +++ b/tests/pos-with-compiler-cc/dotc/transform/CompleteJavaEnums.scala @@ -80,7 +80,7 @@ class CompleteJavaEnums extends MiniPhase with InfoTransformer { thisPhase => parents.map { case app @ Apply(fn, args0) if fn.symbol.owner == targetCls => if args0.nonEmpty && targetCls == defn.JavaEnumClass then - report.error("the constructor of java.lang.Enum cannot be called explicitly", app.sourcePos) + report.error(em"the constructor of java.lang.Enum cannot be called explicitly", app.sourcePos) cpy.Apply(app)(fn, args0 ++ args) case p => p } diff --git a/tests/pos-with-compiler-cc/dotc/transform/ElimRepeated.scala b/tests/pos-with-compiler-cc/dotc/transform/ElimRepeated.scala index bdc2a268c1f8..78baec70bee6 100644 --- a/tests/pos-with-compiler-cc/dotc/transform/ElimRepeated.scala +++ b/tests/pos-with-compiler-cc/dotc/transform/ElimRepeated.scala @@ -51,10 +51,10 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase => // see https://github.com/scala/bug/issues/11714 val validJava = isValidJavaVarArgs(sym.info) if !validJava then - report.error("""To generate java-compatible varargs: + report.error(em"""To generate java-compatible varargs: | - there must be a single repeated parameter | - it must be the last argument in the last parameter list - |""".stripMargin, + |""", sym.sourcePos) else addVarArgsForwarder(sym, isJavaVarargsOverride, hasAnnotation, parentHasAnnotation) diff --git a/tests/pos-with-compiler-cc/dotc/transform/Erasure.scala b/tests/pos-with-compiler-cc/dotc/transform/Erasure.scala index c797c9fd92c1..557b2ac5a4c6 100644 --- a/tests/pos-with-compiler-cc/dotc/transform/Erasure.scala +++ b/tests/pos-with-compiler-cc/dotc/transform/Erasure.scala @@ -549,28 +549,30 @@ object Erasure { /** Check that Java statics and packages can only be used in selections. */ - private def checkNotErased(tree: Tree)(using Context): tree.type = { - if (!ctx.mode.is(Mode.Type)) { + private def checkNotErased(tree: Tree)(using Context): tree.type = + if !ctx.mode.is(Mode.Type) then if isErased(tree) then val msg = if tree.symbol.is(Flags.Inline) then em"""${tree.symbol} is declared as `inline`, but was not inlined | - |Try increasing `-Xmax-inlines` above ${ctx.settings.XmaxInlines.value}""".stripMargin - else em"${tree.symbol} is declared as `erased`, but is in fact used" + |Try increasing `-Xmax-inlines` above ${ctx.settings.XmaxInlines.value}""" + else + em"${tree.symbol} is declared as `erased`, but is in fact used" report.error(msg, tree.srcPos) - tree.symbol.getAnnotation(defn.CompileTimeOnlyAnnot) match { + tree.symbol.getAnnotation(defn.CompileTimeOnlyAnnot) match case Some(annot) => - def defaultMsg = - i"""Reference to ${tree.symbol.showLocated} should not have survived, - |it should have been processed and eliminated during expansion of an enclosing macro or term erasure.""" - val message = annot.argumentConstant(0).fold(defaultMsg)(_.stringValue) + val message = annot.argumentConstant(0) match + case Some(c) => + c.stringValue.toMessage + case _ => + em"""Reference to ${tree.symbol.showLocated} should not have survived, + |it should have been processed and eliminated during expansion of an enclosing macro or term erasure.""" report.error(message, tree.srcPos) case _ => // OK - } - } + checkNotErasedClass(tree) - } + end checkNotErased private def checkNotErasedClass(tp: Type, tree: untpd.Tree)(using Context): Unit = tp match case JavaArrayType(et) => @@ -780,7 +782,7 @@ object Erasure { val tp = originalQual if tp =:= qual1.tpe.widen then return errorTree(qual1, - ex"Unable to emit reference to ${sym.showLocated}, ${sym.owner} is not accessible in ${ctx.owner.enclosingClass}") + em"Unable to emit reference to ${sym.showLocated}, ${sym.owner} is not accessible in ${ctx.owner.enclosingClass}") tp recur(cast(qual1, castTarget)) } diff --git a/tests/pos-with-compiler-cc/dotc/transform/ExpandSAMs.scala b/tests/pos-with-compiler-cc/dotc/transform/ExpandSAMs.scala index cd6753eaed69..0552fe31f8a2 100644 --- a/tests/pos-with-compiler-cc/dotc/transform/ExpandSAMs.scala +++ b/tests/pos-with-compiler-cc/dotc/transform/ExpandSAMs.scala @@ -186,7 +186,7 @@ class ExpandSAMs extends MiniPhase: private def checkRefinements(tpe: Type, tree: Tree)(using Context): Type = tpe.dealias match { case RefinedType(parent, name, _) => if (name.isTermName && tpe.member(name).symbol.ownersIterator.isEmpty) // if member defined in the refinement - report.error("Lambda does not define " + name, tree.srcPos) + report.error(em"Lambda does not define $name", tree.srcPos) checkRefinements(parent, tree) case tpe => tpe diff --git a/tests/pos-with-compiler-cc/dotc/transform/ExplicitOuter.scala b/tests/pos-with-compiler-cc/dotc/transform/ExplicitOuter.scala index 00074a6ea81a..c1536022a2ac 100644 --- a/tests/pos-with-compiler-cc/dotc/transform/ExplicitOuter.scala +++ b/tests/pos-with-compiler-cc/dotc/transform/ExplicitOuter.scala @@ -176,8 +176,9 @@ object ExplicitOuter { if prefix == NoPrefix then outerCls.typeRef.appliedTo(outerCls.typeParams.map(_ => TypeBounds.empty)) else prefix.widen) val info = if (flags.is(Method)) ExprType(target) else target + val currentNestingLevel = ctx.nestingLevel atPhaseNoEarlier(explicitOuterPhase.next) { // outer accessors are entered at explicitOuter + 1, should not be defined before. - newSymbol(owner, name, SyntheticArtifact | flags, info, coord = cls.coord) + newSymbol(owner, name, SyntheticArtifact | flags, info, coord = cls.coord, nestingLevel = currentNestingLevel) } } diff --git a/tests/pos-with-compiler-cc/dotc/transform/InlineVals.scala b/tests/pos-with-compiler-cc/dotc/transform/InlineVals.scala index 65212ec2c0cc..047a187bad68 100644 --- a/tests/pos-with-compiler-cc/dotc/transform/InlineVals.scala +++ b/tests/pos-with-compiler-cc/dotc/transform/InlineVals.scala @@ -38,8 +38,8 @@ class InlineVals extends MiniPhase: tpt.tpe.widenTermRefExpr.dealiasKeepOpaques.normalized match case tp: ConstantType => if !isPureExpr(rhs) then - val details = if enclosingInlineds.isEmpty then "" else em"but was: $rhs" - report.error(s"inline value must be pure$details", rhs.srcPos) + def details = if enclosingInlineds.isEmpty then "" else i"but was: $rhs" + report.error(em"inline value must be pure$details", rhs.srcPos) case tp => if tp.typeSymbol.is(Opaque) then report.error(em"The type of an `inline val` cannot be an opaque type.\n\nTo inline, consider using `inline def` instead", rhs) diff --git a/tests/pos-with-compiler-cc/dotc/transform/LazyVals.scala b/tests/pos-with-compiler-cc/dotc/transform/LazyVals.scala index c32ea61cff2b..3b37ef130231 100644 --- a/tests/pos-with-compiler-cc/dotc/transform/LazyVals.scala +++ b/tests/pos-with-compiler-cc/dotc/transform/LazyVals.scala @@ -2,30 +2,33 @@ package dotty.tools.dotc package transform import java.util.IdentityHashMap - import ast.tpd import core.Annotations.Annotation import core.Constants.Constant -import core.Contexts._ -import core.Decorators._ +import core.Contexts.* +import core.Decorators.* import core.DenotTransformers.IdentityDenotTransformer -import core.Flags._ -import core.NameKinds.{LazyBitMapName, LazyLocalInitName, LazyLocalName, ExpandedName} +import core.Flags.* +import core.NameKinds.{ExpandedName, LazyBitMapName, LazyLocalInitName, LazyLocalName} import core.StdNames.nme -import core.Symbols._ -import core.Types._ +import core.Symbols.* +import core.Types.* import core.{Names, StdNames} +import dotty.tools.dotc.config.Feature import transform.MegaPhase.MiniPhase -import transform.SymUtils._ +import transform.SymUtils.* + import scala.collection.mutable class LazyVals extends MiniPhase with IdentityDenotTransformer { import LazyVals._ import tpd._ - /** this map contains mutable state of transformation: OffsetDefs to be appended to companion object definitions, - * and number of bits currently used */ - class OffsetInfo(var defs: List[Tree], var ord:Int) + /** + * The map contains the list of the offset trees. + */ + class OffsetInfo(var defs: List[Tree], var ord: Int = 0) + private val appendOffsetDefs = mutable.Map.empty[Symbol, OffsetInfo] override def phaseName: String = LazyVals.name @@ -52,6 +55,7 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { else nullables.toList } + private def needsBoxing(tp: Type)(using Context): Boolean = tp.classSymbol.isPrimitiveValueClass override def prepareForUnit(tree: Tree)(using Context): Context = { if (lazyValNullables == null) @@ -62,7 +66,6 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { override def transformDefDef(tree: DefDef)(using Context): Tree = transformLazyVal(tree) - override def transformValDef(tree: ValDef)(using Context): Tree = transformLazyVal(tree) @@ -103,10 +106,9 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { /** Append offset fields to companion objects - */ + */ override def transformTemplate(template: Template)(using Context): Tree = { val cls = ctx.owner.asClass - appendOffsetDefs.get(cls) match { case None => template case Some(data) => @@ -115,7 +117,6 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { } } - private def addInFront(prefix: List[Tree], stats: List[Tree]) = stats match { case first :: rest if isSuperConstrCall(first) => first :: prefix ::: rest case _ => prefix ::: stats @@ -186,7 +187,6 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { Thicket(holderTree, initTree, accessor) } - override def transformStats(trees: List[tpd.Tree])(using Context): List[Tree] = { // backend requires field usage to be after field definition // need to bring containers to start of method @@ -274,6 +274,231 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { } } + /** + * Create a threadsafe lazy accessor and function that computes the field's value. `Evaluating` and + * `NullValue` are represented by `object`s and `Waiting` by a class that allows awaiting the completion + * of the evaluation. Note that since tail-recursive functions are transformed *before* lazy-vals, + * this implementation does involve explicit while loop. `PatternMatcher` is coming before `LazyVals`, + * therefore the pattern matching is implemented using if-s. + * + * ``` + * private @volatile var _x: AnyRef = null + * + * def x: A = + * val result = _x + * if result.isInstanceOf[A] then + * result // possible unboxing applied here + * else if result.eq(NullValue) then + * null // possible unboxing applied here + * else + * x_compute() // possible unboxing applied here + * + * private def x_compute(): AnyRef = + * while do + * val current: AnyRef = _x + * if current.eq(null) then + * if CAS(_x, null, Evaluating) then + * var resultNullable: AnyRef = null + * var result: AnyRef = null + * try + * resultNullable = rhs + * nullable = null // nulls out the nullable fields used only in initialization + * if resultNullable.eq(null) then + * result = NullValue + * else + * result = resultNullable + * finally + * if !CAS(_x, Evaluating, result) then + * val lock = _x.asInstanceOf[Waiting] + * CAS(_x, lock, result) + * lock.release() + * return resultNullable + * else + * if current.isInstanceOf[LazyValControlState] then + * if current.eq(Evaluating) then // To avoid creating Waiting instance + * CAS(current, current, new Waiting) + * else if current.isInstanceOf[Waiting] then + * current.asInstanceOf[Waiting].await() + * else return null + * else + * return current + * end while + * * ``` + * + * @param memberDef the transformed lazy field member definition + * @param claz the class containing this lazy val field + * @param target the target synthetic field + * @param offset the offset of the field in the storage allocation of the class + * @param thiz a reference to the transformed class + */ + def mkThreadSafeDef(memberDef: ValOrDefDef, + claz: ClassSymbol, + target: Symbol, + offset: Tree, + thiz: Tree)(using Context): (DefDef, DefDef) = { + val tp = memberDef.tpe.widenDealias.resultType.widenDealias + val waiting = ref(defn.LazyValsWaitingState) + val controlState = ref(defn.LazyValsControlState) + val evaluating = Select(ref(defn.LazyValsModule), lazyNme.RLazyVals.evaluating) + val nullValue = Select(ref(defn.LazyValsModule), lazyNme.RLazyVals.nullValue) + val objCasFlag = Select(ref(defn.LazyValsModule), lazyNme.RLazyVals.objCas) + val accessorMethodSymbol = memberDef.symbol.asTerm + val lazyInitMethodName = LazyLocalInitName.fresh(memberDef.name.asTermName) + val lazyInitMethodSymbol = newSymbol(claz, lazyInitMethodName, Synthetic | Method | Private, MethodType(Nil)(_ => Nil, _ => defn.ObjectType)) + + val rhs = memberDef.rhs + val rhsMappedOwner = rhs.changeOwnerAfter(memberDef.symbol, lazyInitMethodSymbol, this) + val valueSymbol = newSymbol(accessorMethodSymbol, lazyNme.result, Synthetic, defn.ObjectType) + + val immediateValueCondition = + if (defn.LazyValsControlState.isSubClass(tp.classSymbol)) then + ref(valueSymbol).select(defn.Any_!=).appliedTo(nullLiteral).select(nme.And).appliedTo(ref(valueSymbol) + .select(defn.Any_isInstanceOf).appliedToType(defn.LazyValsControlState.typeRef) + .select(nme.UNARY_!).appliedToNone) + else + ref(valueSymbol).select(defn.Any_isInstanceOf).appliedToType(tp) + + val accessorBody = + Block( + ValDef(valueSymbol, ref(target)) :: Nil, + If( // if _x != null && !_x.isInstanceOf[LazyValControlState] then + immediateValueCondition, + ref(valueSymbol).ensureConforms(tp), // then return _x.asInstanceOf[A] + If( + ref(valueSymbol).select(defn.Object_eq).appliedTo(nullValue), + nullLiteral.ensureConforms(tp), + ref(lazyInitMethodSymbol).ensureApplied.ensureConforms(tp) // else return x_compute() + ) + ) + ) + + val accessorDef = DefDef(accessorMethodSymbol, accessorBody) + + // if observed a null (uninitialized) value + val initialize = { + // var result: AnyRef + val resSymbNullable = newSymbol(lazyInitMethodSymbol, lazyNme.resultNullable, Synthetic | Mutable, defn.ObjectType) + val resSymb = newSymbol(lazyInitMethodSymbol, lazyNme.result, Synthetic | Mutable, defn.ObjectType) + // releasing block in finally + val lockRel = { + val lockSymb = newSymbol(lazyInitMethodSymbol, lazyNme.lock, Synthetic, waiting.typeOpt) + Block(ValDef(lockSymb, ref(target).cast(waiting.typeOpt)) + :: objCasFlag.appliedTo(thiz, offset, ref(lockSymb), ref(resSymb)) :: Nil, + ref(lockSymb).select(lazyNme.RLazyVals.waitingRelease).ensureApplied) + } + // finally block + val fin = If( + objCasFlag.appliedTo(thiz, offset, evaluating, ref(resSymb)).select(nme.UNARY_!).appliedToNone, + lockRel, + unitLiteral + ) + // entire try block + val evaluate = Try( + + Block( + (Assign(ref(resSymbNullable), if needsBoxing(tp) && rhsMappedOwner != EmptyTree then rhsMappedOwner.ensureConforms(defn.boxedType(tp)) else rhsMappedOwner) // try result = rhs + :: If( + ref(resSymbNullable).select(defn.Object_eq).appliedTo(nullLiteral), + Assign(ref(resSymb), nullValue), + Assign(ref(resSymb), ref(resSymbNullable)) + ) :: Nil) + ::: nullOut(nullableFor(accessorMethodSymbol)), + unitLiteral), + Nil, + fin + ) + // if CAS(_, null, Evaluating) + If( + objCasFlag.appliedTo(thiz, offset, nullLiteral, evaluating), + Block(ValDef(resSymb, nullLiteral) :: ValDef(resSymbNullable, nullLiteral) :: evaluate :: Nil, // var result: AnyRef = null + Return(ref(resSymbNullable), lazyInitMethodSymbol)), + unitLiteral + ).withType(defn.UnitType) + } + + val current = newSymbol(lazyInitMethodSymbol, lazyNme.current, Synthetic, defn.ObjectType) + val ifNotUninitialized = + If( + ref(current).select(defn.Any_isInstanceOf).appliedToTypeTree(controlState), + // if a control state + If( + ref(current).select(defn.Object_eq).appliedTo(evaluating), + // if is Evaluating then CAS(_, Evaluating, new Waiting) + Block( + objCasFlag.appliedTo(thiz, offset, ref(current), Select(New(waiting), StdNames.nme.CONSTRUCTOR).ensureApplied) :: Nil, + unitLiteral + ), + // if not Evaluating + If( + ref(current).select(defn.Any_isInstanceOf).appliedToTypeTree(waiting), + // if is waiting + ref(current).select(defn.Any_asInstanceOf).appliedToTypeTree(waiting).select(lazyNme.RLazyVals.waitingAwaitRelease, _.info.paramInfoss.exists(_.size == 0)).ensureApplied, + Return(nullLiteral, lazyInitMethodSymbol) + ) + ), + // if not a control state + Return(ref(current), lazyInitMethodSymbol) + ) + + val initBody = Block(ValDef(current, ref(target)) :: Nil, If(ref(current).select(defn.Object_eq).appliedTo(nullLiteral), initialize, ifNotUninitialized).withType(defn.UnitType)) + val initMainLoop = WhileDo(EmptyTree, initBody) // becomes: while (true) do { body } + val initMethodDef = DefDef(lazyInitMethodSymbol, initMainLoop) + (accessorDef, initMethodDef) + } + + def transformMemberDefThreadSafe(x: ValOrDefDef)(using Context): Thicket = { + assert(!(x.symbol is Mutable)) + if ctx.settings.YlightweightLazyVals.value then + transformMemberDefThreadSafeNew(x) + else + transformMemberDefThreadSafeLegacy(x) + } + + def transformMemberDefThreadSafeNew(x: ValOrDefDef)(using Context): Thicket = { + import dotty.tools.dotc.core.Types._ + import dotty.tools.dotc.core.Flags._ + + val claz = x.symbol.owner.asClass + val thizClass = Literal(Constant(claz.info)) + + def offsetName(id: Int) = s"${StdNames.nme.LAZY_FIELD_OFFSET}${if (x.symbol.owner.is(Module)) "_m_" else ""}$id".toTermName + val containerName = LazyLocalName.fresh(x.name.asTermName) + val containerSymbol = newSymbol(claz, containerName, x.symbol.flags &~ containerFlagsMask | containerFlags | Private, defn.ObjectType, coord = x.symbol.coord).enteredAfter(this) + containerSymbol.addAnnotation(Annotation(defn.VolatileAnnot)) // private @volatile var _x: AnyRef + containerSymbol.addAnnotations(x.symbol.annotations) // pass annotations from original definition + val stat = x.symbol.isStatic + if stat then + containerSymbol.setFlag(JavaStatic) + val getOffset = + if stat then + Select(ref(defn.LazyValsModule), lazyNme.RLazyVals.getStaticFieldOffset) + else + Select(ref(defn.LazyValsModule), lazyNme.RLazyVals.getOffsetStatic) + val containerTree = ValDef(containerSymbol, nullLiteral) + + // create an offset for this lazy val + val offsetSymbol: TermSymbol = appendOffsetDefs.get(claz) match + case Some(info) => + newSymbol(claz, offsetName(info.defs.size), Synthetic, defn.LongType).enteredAfter(this) + case None => + newSymbol(claz, offsetName(0), Synthetic, defn.LongType).enteredAfter(this) + offsetSymbol.nn.addAnnotation(Annotation(defn.ScalaStaticAnnot)) + val fieldTree = thizClass.select(lazyNme.RLazyVals.getDeclaredField).appliedTo(Literal(Constant(containerName.mangledString))) + val offsetTree = ValDef(offsetSymbol.nn, getOffset.appliedTo(fieldTree)) + val offsetInfo = appendOffsetDefs.getOrElseUpdate(claz, new OffsetInfo(Nil)) + offsetInfo.defs = offsetTree :: offsetInfo.defs + val offset = ref(offsetSymbol.nn) + + val swapOver = + if stat then + tpd.clsOf(x.symbol.owner.typeRef) + else + This(claz) + + val (accessorDef, initMethodDef) = mkThreadSafeDef(x, claz, containerSymbol, offset, swapOver) + Thicket(containerTree, accessorDef, initMethodDef) + } + /** Create a threadsafe lazy accessor equivalent to such code * ``` * def methodSymbol(): Int = { @@ -305,7 +530,7 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { * } * ``` */ - def mkThreadSafeDef(methodSymbol: TermSymbol, + def mkThreadSafeDefLegacy(methodSymbol: TermSymbol, claz: ClassSymbol, ord: Int, target: Symbol, @@ -374,15 +599,12 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { DefDef(methodSymbol, loop) } - def transformMemberDefThreadSafe(x: ValOrDefDef)(using Context): Thicket = { - assert(!(x.symbol is Mutable)) - + def transformMemberDefThreadSafeLegacy(x: ValOrDefDef)(using Context): Thicket = { val tpe = x.tpe.widen.resultType.widen val claz = x.symbol.owner.asClass val thizClass = Literal(Constant(claz.info)) - val helperModule = requiredModule("scala.runtime.LazyVals") - val getOffset = Select(ref(helperModule), lazyNme.RLazyVals.getOffset) - val getOffsetStatic = Select(ref(helperModule), lazyNme.RLazyVals.getOffsetStatic) + val getOffset = Select(ref(defn.LazyValsModule), lazyNme.RLazyVals.getOffset) + val getOffsetStatic = Select(ref(defn.LazyValsModule), lazyNme.RLazyVals.getOffsetStatic) var offsetSymbol: TermSymbol | Null = null var flag: Tree = EmptyTree var ord = 0 @@ -425,17 +647,16 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { val containerName = LazyLocalName.fresh(x.name.asTermName) val containerSymbol = newSymbol(claz, containerName, x.symbol.flags &~ containerFlagsMask | containerFlags, tpe, coord = x.symbol.coord).enteredAfter(this) - val containerTree = ValDef(containerSymbol, defaultValue(tpe)) val offset = ref(offsetSymbol.nn) - val getFlag = Select(ref(helperModule), lazyNme.RLazyVals.get) - val setFlag = Select(ref(helperModule), lazyNme.RLazyVals.setFlag) - val wait = Select(ref(helperModule), lazyNme.RLazyVals.wait4Notification) - val state = Select(ref(helperModule), lazyNme.RLazyVals.state) - val cas = Select(ref(helperModule), lazyNme.RLazyVals.cas) + val getFlag = Select(ref(defn.LazyValsModule), lazyNme.RLazyVals.get) + val setFlag = Select(ref(defn.LazyValsModule), lazyNme.RLazyVals.setFlag) + val wait = Select(ref(defn.LazyValsModule), lazyNme.RLazyVals.wait4Notification) + val state = Select(ref(defn.LazyValsModule), lazyNme.RLazyVals.state) + val cas = Select(ref(defn.LazyValsModule), lazyNme.RLazyVals.cas) - val accessor = mkThreadSafeDef(x.symbol.asTerm, claz, ord, containerSymbol, x.rhs, tpe, offset, getFlag, state, cas, setFlag, wait) + val accessor = mkThreadSafeDefLegacy(x.symbol.asTerm, claz, ord, containerSymbol, x.rhs, tpe, offset, getFlag, state, cas, setFlag, wait) if (flag eq EmptyTree) Thicket(containerTree, accessor) else Thicket(containerTree, flag, accessor) @@ -445,26 +666,35 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { object LazyVals { val name: String = "lazyVals" val description: String = "expand lazy vals" - object lazyNme { import Names.TermName object RLazyVals { import scala.runtime.LazyVals.{Names => N} - val get: TermName = N.get.toTermName - val setFlag: TermName = N.setFlag.toTermName - val wait4Notification: TermName = N.wait4Notification.toTermName - val state: TermName = N.state.toTermName - val cas: TermName = N.cas.toTermName - val getOffset: TermName = N.getOffset.toTermName - val getOffsetStatic: TermName = "getOffsetStatic".toTermName - val getDeclaredField: TermName = "getDeclaredField".toTermName + val waitingAwaitRelease: TermName = "await".toTermName + val waitingRelease: TermName = "countDown".toTermName + val evaluating: TermName = "Evaluating".toTermName + val nullValue: TermName = "NullValue".toTermName + val objCas: TermName = "objCAS".toTermName + val get: TermName = N.get.toTermName + val setFlag: TermName = N.setFlag.toTermName + val wait4Notification: TermName = N.wait4Notification.toTermName + val state: TermName = N.state.toTermName + val cas: TermName = N.cas.toTermName + val getOffset: TermName = N.getOffset.toTermName + val getOffsetStatic: TermName = "getOffsetStatic".toTermName + val getStaticFieldOffset: TermName = "getStaticFieldOffset".toTermName + val getDeclaredField: TermName = "getDeclaredField".toTermName } val flag: TermName = "flag".toTermName val state: TermName = "state".toTermName val result: TermName = "result".toTermName + val resultNullable: TermName = "resultNullable".toTermName val value: TermName = "value".toTermName val initialized: TermName = "initialized".toTermName val initialize: TermName = "initialize".toTermName val retry: TermName = "retry".toTermName + val current: TermName = "current".toTermName + val lock: TermName = "lock".toTermName + val discard: TermName = "discard".toTermName } } diff --git a/tests/pos-with-compiler-cc/dotc/transform/NonLocalReturns.scala b/tests/pos-with-compiler-cc/dotc/transform/NonLocalReturns.scala index 7e1ae9e661f6..ddf858994220 100644 --- a/tests/pos-with-compiler-cc/dotc/transform/NonLocalReturns.scala +++ b/tests/pos-with-compiler-cc/dotc/transform/NonLocalReturns.scala @@ -6,6 +6,7 @@ import Contexts._, Symbols._, Types._, Flags._, StdNames._ import MegaPhase._ import NameKinds.NonLocalReturnKeyName import config.SourceVersion.* +import Decorators.em object NonLocalReturns { import ast.tpd._ @@ -96,7 +97,7 @@ class NonLocalReturns extends MiniPhase { override def transformReturn(tree: Return)(using Context): Tree = if isNonLocalReturn(tree) then report.gradualErrorOrMigrationWarning( - "Non local returns are no longer supported; use scala.util.control.NonLocalReturns instead", + em"Non local returns are no longer supported; use scala.util.control.NonLocalReturns instead", tree.srcPos, warnFrom = `3.2`, errorFrom = future) diff --git a/tests/pos-with-compiler-cc/dotc/transform/PCPCheckAndHeal.scala b/tests/pos-with-compiler-cc/dotc/transform/PCPCheckAndHeal.scala index 263b0040eb24..9e6c54b85304 100644 --- a/tests/pos-with-compiler-cc/dotc/transform/PCPCheckAndHeal.scala +++ b/tests/pos-with-compiler-cc/dotc/transform/PCPCheckAndHeal.scala @@ -246,13 +246,14 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages( checkStable(tp, pos, "type witness") getQuoteTypeTags.getTagRef(tp) case _: SearchFailureType => - report.error(i"""Reference to $tp within quotes requires a given $reqType in scope. - |${ctx.typer.missingArgMsg(tag, reqType, "")} - | - |""", pos) + report.error( + ctx.typer.missingArgMsg(tag, reqType, "") + .prepend(i"Reference to $tp within quotes requires a given $reqType in scope.\n") + .append("\n"), + pos) tp case _ => - report.error(i"""Reference to $tp within quotes requires a given $reqType in scope. + report.error(em"""Reference to $tp within quotes requires a given $reqType in scope. | |""", pos) tp diff --git a/tests/pos-with-compiler-cc/dotc/transform/Pickler.scala b/tests/pos-with-compiler-cc/dotc/transform/Pickler.scala index 4d9b42a36fe7..b862ca7fe01e 100644 --- a/tests/pos-with-compiler-cc/dotc/transform/Pickler.scala +++ b/tests/pos-with-compiler-cc/dotc/transform/Pickler.scala @@ -11,7 +11,7 @@ import Periods._ import Phases._ import Symbols._ import Flags.Module -import reporting.{ThrowingReporter, Profile} +import reporting.{ThrowingReporter, Profile, Message} import collection.mutable import scala.concurrent.{Future, Await, ExecutionContext} import scala.concurrent.duration.Duration @@ -71,7 +71,7 @@ class Pickler extends Phase { val treePkl = new TreePickler(pickler) treePkl.pickle(tree :: Nil) Profile.current.recordTasty(treePkl.buf.length) - val positionWarnings = new mutable.ListBuffer[String]() + val positionWarnings = new mutable.ListBuffer[Message]() val pickledF = inContext(ctx.fresh) { Future { treePkl.compactify() @@ -147,8 +147,8 @@ class Pickler extends Phase { if unequal then output("before-pickling.txt", previous) output("after-pickling.txt", unpickled) - report.error(s"""pickling difference for $cls in ${cls.source}, for details: - | - | diff before-pickling.txt after-pickling.txt""".stripMargin) + report.error(em"""pickling difference for $cls in ${cls.source}, for details: + | + | diff before-pickling.txt after-pickling.txt""") end testSame } diff --git a/tests/pos-with-compiler-cc/dotc/transform/PostTyper.scala b/tests/pos-with-compiler-cc/dotc/transform/PostTyper.scala index 2648e19e23a8..05aaa745bb18 100644 --- a/tests/pos-with-compiler-cc/dotc/transform/PostTyper.scala +++ b/tests/pos-with-compiler-cc/dotc/transform/PostTyper.scala @@ -362,6 +362,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase } case Inlined(call, bindings, expansion) if !call.isEmpty => val pos = call.sourcePos + CrossVersionChecks.checkExperimentalRef(call.symbol, pos) val callTrace = Inlines.inlineCallTrace(call.symbol, pos)(using ctx.withSource(pos.source)) cpy.Inlined(tree)(callTrace, transformSub(bindings), transform(expansion)(using inlineContext(call))) case templ: Template => diff --git a/tests/pos-with-compiler-cc/dotc/transform/ProtectedAccessors.scala b/tests/pos-with-compiler-cc/dotc/transform/ProtectedAccessors.scala index 98e835293303..6d8f7bdb32cb 100644 --- a/tests/pos-with-compiler-cc/dotc/transform/ProtectedAccessors.scala +++ b/tests/pos-with-compiler-cc/dotc/transform/ProtectedAccessors.scala @@ -70,7 +70,7 @@ class ProtectedAccessors extends MiniPhase { override def ifNoHost(reference: RefTree)(using Context): Tree = { val curCls = ctx.owner.enclosingClass transforms.println(i"${curCls.ownersIterator.toList}%, %") - report.error(i"illegal access to protected ${reference.symbol.showLocated} from $curCls", + report.error(em"illegal access to protected ${reference.symbol.showLocated} from $curCls", reference.srcPos) reference } diff --git a/tests/pos-with-compiler-cc/dotc/transform/Recheck.scala b/tests/pos-with-compiler-cc/dotc/transform/Recheck.scala index 0ac9087a08c0..c524bbb7702f 100644 --- a/tests/pos-with-compiler-cc/dotc/transform/Recheck.scala +++ b/tests/pos-with-compiler-cc/dotc/transform/Recheck.scala @@ -261,7 +261,9 @@ abstract class Recheck extends Phase, SymTransformer: mt.instantiate(argTypes) def recheckApply(tree: Apply, pt: Type)(using Context): Type = - val funtpe = recheck(tree.fun) + val funTp = recheck(tree.fun) + // reuse the tree's type on signature polymorphic methods, instead of using the (wrong) rechecked one + val funtpe = if tree.fun.symbol.originalSignaturePolymorphic.exists then tree.fun.tpe else funTp funtpe.widen match case fntpe: MethodType => assert(fntpe.paramInfos.hasSameLengthAs(tree.args)) diff --git a/tests/pos-with-compiler-cc/dotc/transform/Splicer.scala b/tests/pos-with-compiler-cc/dotc/transform/Splicer.scala index da1cf4e9a44e..09c820169179 100644 --- a/tests/pos-with-compiler-cc/dotc/transform/Splicer.scala +++ b/tests/pos-with-compiler-cc/dotc/transform/Splicer.scala @@ -81,10 +81,10 @@ object Splicer { ref(defn.Predef_undefined).withType(ErrorType(ex.msg)) case NonFatal(ex) => val msg = - s"""Failed to evaluate macro. - | Caused by ${ex.getClass}: ${if (ex.getMessage == null) "" else ex.getMessage} - | ${ex.getStackTrace.takeWhile(_.getClassName != "dotty.tools.dotc.transform.Splicer$").drop(1).mkString("\n ")} - """.stripMargin + em"""Failed to evaluate macro. + | Caused by ${ex.getClass}: ${if (ex.getMessage == null) "" else ex.getMessage} + | ${ex.getStackTrace.takeWhile(_.getClassName != "dotty.tools.dotc.transform.Splicer$").drop(1).mkString("\n ")} + """ report.error(msg, spliceExpansionPos) ref(defn.Predef_undefined).withType(ErrorType(msg)) } diff --git a/tests/pos-with-compiler-cc/dotc/transform/SuperAccessors.scala b/tests/pos-with-compiler-cc/dotc/transform/SuperAccessors.scala index b0c8605e7dd1..2307f759b571 100644 --- a/tests/pos-with-compiler-cc/dotc/transform/SuperAccessors.scala +++ b/tests/pos-with-compiler-cc/dotc/transform/SuperAccessors.scala @@ -88,7 +88,7 @@ class SuperAccessors(thisPhase: DenotTransformer) { // Diagnostic for SI-7091 if (!accDefs.contains(clazz)) report.error( - s"Internal error: unable to store accessor definition in ${clazz}. clazz.hasPackageFlag=${clazz.is(Package)}. Accessor required for ${sel} (${sel.show})", + em"Internal error: unable to store accessor definition in ${clazz}. clazz.hasPackageFlag=${clazz.is(Package)}. Accessor required for ${sel.toString} ($sel)", sel.srcPos) else accDefs(clazz) += DefDef(acc, EmptyTree).withSpan(accRange) acc @@ -109,16 +109,16 @@ class SuperAccessors(thisPhase: DenotTransformer) { if (sym.isTerm && !sym.is(Method, butNot = Accessor) && !ctx.owner.isAllOf(ParamForwarder)) // ParamForwaders as installed ParamForwarding.scala do use super calls to vals - report.error(s"super may be not be used on ${sym.underlyingSymbol}", sel.srcPos) + report.error(em"super may be not be used on ${sym.underlyingSymbol}", sel.srcPos) else if (isDisallowed(sym)) - report.error(s"super not allowed here: use this.${sel.name} instead", sel.srcPos) + report.error(em"super not allowed here: use this.${sel.name} instead", sel.srcPos) else if (sym.is(Deferred)) { val member = sym.overridingSymbol(clazz.asClass) if (!mix.name.isEmpty || !member.exists || !(member.is(AbsOverride) && member.isIncompleteIn(clazz))) report.error( - i"${sym.showLocated} is accessed from super. It may not be abstract unless it is overridden by a member declared `abstract' and `override'", + em"${sym.showLocated} is accessed from super. It may not be abstract unless it is overridden by a member declared `abstract' and `override'", sel.srcPos) else report.log(i"ok super $sel ${sym.showLocated} $member $clazz ${member.isIncompleteIn(clazz)}") } @@ -131,7 +131,7 @@ class SuperAccessors(thisPhase: DenotTransformer) { val overriding = sym.overridingSymbol(intermediateClass) if (overriding.is(Deferred, butNot = AbsOverride) && !overriding.owner.is(Trait)) report.error( - s"${sym.showLocated} cannot be directly accessed from ${clazz} because ${overriding.owner} redeclares it as abstract", + em"${sym.showLocated} cannot be directly accessed from ${clazz} because ${overriding.owner} redeclares it as abstract", sel.srcPos) } else { diff --git a/tests/pos-with-compiler-cc/dotc/transform/TailRec.scala b/tests/pos-with-compiler-cc/dotc/transform/TailRec.scala index 71b66c3d0da6..741b9d1627fe 100644 --- a/tests/pos-with-compiler-cc/dotc/transform/TailRec.scala +++ b/tests/pos-with-compiler-cc/dotc/transform/TailRec.scala @@ -4,7 +4,7 @@ package transform import ast.{TreeTypeMap, tpd} import config.Printers.tailrec import core.* -import Contexts.*, Flags.*, Symbols.* +import Contexts.*, Flags.*, Symbols.*, Decorators.em import Constants.Constant import NameKinds.{TailLabelName, TailLocalName, TailTempName} import StdNames.nme @@ -303,7 +303,7 @@ class TailRec extends MiniPhase { def fail(reason: String) = { if (isMandatory) { failureReported = true - report.error(s"Cannot rewrite recursive call: $reason", tree.srcPos) + report.error(em"Cannot rewrite recursive call: $reason", tree.srcPos) } else tailrec.println("Cannot rewrite recursive call at: " + tree.span + " because: " + reason) diff --git a/tests/pos-with-compiler-cc/dotc/transform/TreeChecker.scala b/tests/pos-with-compiler-cc/dotc/transform/TreeChecker.scala index dc8defa90eef..f11c8f5313dc 100644 --- a/tests/pos-with-compiler-cc/dotc/transform/TreeChecker.scala +++ b/tests/pos-with-compiler-cc/dotc/transform/TreeChecker.scala @@ -417,11 +417,11 @@ class TreeChecker extends Phase with SymTransformer { sym == mbr || sym.overriddenSymbol(mbr.owner.asClass) == mbr || mbr.overriddenSymbol(sym.owner.asClass) == sym), - ex"""symbols differ for $tree - |was : $sym - |alternatives by type: $memberSyms%, % of types ${memberSyms.map(_.info)}%, % - |qualifier type : ${qualTpe} - |tree type : ${tree.typeOpt} of class ${tree.typeOpt.getClass}""") + i"""symbols differ for $tree + |was : $sym + |alternatives by type: $memberSyms%, % of types ${memberSyms.map(_.info)}%, % + |qualifier type : ${qualTpe} + |tree type : ${tree.typeOpt} of class ${tree.typeOpt.getClass}""") } checkNotRepeated(super.typedSelect(tree, pt)) diff --git a/tests/pos-with-compiler-cc/dotc/transform/TupleOptimizations.scala b/tests/pos-with-compiler-cc/dotc/transform/TupleOptimizations.scala index 6bc2f438eb37..6fba0bca4ce3 100644 --- a/tests/pos-with-compiler-cc/dotc/transform/TupleOptimizations.scala +++ b/tests/pos-with-compiler-cc/dotc/transform/TupleOptimizations.scala @@ -145,7 +145,7 @@ class TupleOptimizations extends MiniPhase with IdentityDenotTransformer { val size = tpes.size val n = nTpe.value.intValue if (n < 0 || n >= size) { - report.error("index out of bounds: " + n, nTree.underlyingArgument.srcPos) + report.error(em"index out of bounds: $n", nTree.underlyingArgument.srcPos) tree } else if (size <= MaxTupleArity) @@ -155,7 +155,7 @@ class TupleOptimizations extends MiniPhase with IdentityDenotTransformer { // tup.asInstanceOf[TupleXXL].productElement(n) tup.asInstance(defn.TupleXXLClass.typeRef).select(nme.productElement).appliedTo(Literal(nTpe.value)) case (None, nTpe: ConstantType) if nTpe.value.intValue < 0 => - report.error("index out of bounds: " + nTpe.value.intValue, nTree.srcPos) + report.error(em"index out of bounds: ${nTpe.value.intValue}", nTree.srcPos) tree case _ => // No optimization, keep: diff --git a/tests/pos-with-compiler-cc/dotc/transform/TypeTestsCasts.scala b/tests/pos-with-compiler-cc/dotc/transform/TypeTestsCasts.scala index 7db89300e710..0fc92d774463 100644 --- a/tests/pos-with-compiler-cc/dotc/transform/TypeTestsCasts.scala +++ b/tests/pos-with-compiler-cc/dotc/transform/TypeTestsCasts.scala @@ -242,7 +242,7 @@ object TypeTestsCasts { val foundEffectiveClass = effectiveClass(expr.tpe.widen) if foundEffectiveClass.isPrimitiveValueClass && !testCls.isPrimitiveValueClass then - report.error(i"cannot test if value of $exprType is a reference of $testCls", tree.srcPos) + report.error(em"cannot test if value of $exprType is a reference of $testCls", tree.srcPos) false else foundClasses.exists(check) end checkSensical @@ -346,7 +346,7 @@ object TypeTestsCasts { val testWidened = testType.widen defn.untestableClasses.find(testWidened.isRef(_)) match case Some(untestable) => - report.error(i"$untestable cannot be used in runtime type tests", tree.srcPos) + report.error(em"$untestable cannot be used in runtime type tests", tree.srcPos) constant(expr, Literal(Constant(false))) case _ => val erasedTestType = erasure(testType) @@ -360,7 +360,7 @@ object TypeTestsCasts { if !isTrusted && !isUnchecked then val whyNot = whyUncheckable(expr.tpe, argType, tree.span) if whyNot.nonEmpty then - report.uncheckedWarning(i"the type test for $argType cannot be checked at runtime because $whyNot", expr.srcPos) + report.uncheckedWarning(em"the type test for $argType cannot be checked at runtime because $whyNot", expr.srcPos) transformTypeTest(expr, argType, flagUnrelated = enclosingInlineds.isEmpty) // if test comes from inlined code, dont't flag it even if it always false } diff --git a/tests/pos-with-compiler-cc/dotc/transform/TypeUtils.scala b/tests/pos-with-compiler-cc/dotc/transform/TypeUtils.scala index 5b6e36343379..a897503ef275 100644 --- a/tests/pos-with-compiler-cc/dotc/transform/TypeUtils.scala +++ b/tests/pos-with-compiler-cc/dotc/transform/TypeUtils.scala @@ -76,7 +76,7 @@ object TypeUtils { case AndType(tp1, tp2) => // We assume that we have the following property: // (T1, T2, ..., Tn) & (U1, U2, ..., Un) = (T1 & U1, T2 & U2, ..., Tn & Un) - tp1.tupleElementTypes.zip(tp2.tupleElementTypes).map { case (t1, t2) => t1 & t2 } + tp1.tupleElementTypes.zip(tp2.tupleElementTypes).map { case (t1, t2) => t1.intersect(t2) } case OrType(tp1, tp2) => None // We can't combine the type of two tuples case _ => diff --git a/tests/pos-with-compiler-cc/dotc/transform/patmat/Space.scala b/tests/pos-with-compiler-cc/dotc/transform/patmat/Space.scala index ca0e149f881f..8e891f822255 100644 --- a/tests/pos-with-compiler-cc/dotc/transform/patmat/Space.scala +++ b/tests/pos-with-compiler-cc/dotc/transform/patmat/Space.scala @@ -306,6 +306,7 @@ object SpaceEngine { val isEmptyTp = extractorMemberType(unappResult, nme.isEmpty, NoSourcePosition) isEmptyTp <:< ConstantType(Constant(false)) } + || unappResult.derivesFrom(defn.NonEmptyTupleClass) } /** Is the unapply or unapplySeq irrefutable? diff --git a/tests/pos-with-compiler-cc/dotc/transform/sjs/AddLocalJSFakeNews.scala b/tests/pos-with-compiler-cc/dotc/transform/sjs/AddLocalJSFakeNews.scala index 8851e641122f..6471e58d4ddc 100644 --- a/tests/pos-with-compiler-cc/dotc/transform/sjs/AddLocalJSFakeNews.scala +++ b/tests/pos-with-compiler-cc/dotc/transform/sjs/AddLocalJSFakeNews.scala @@ -65,7 +65,7 @@ class AddLocalJSFakeNews extends MiniPhase { thisPhase => constant.typeValue.typeSymbol.asClass case _ => // this shouldn't happen - report.error(i"unexpected $classValueArg for the first argument to `createLocalJSClass`", classValueArg) + report.error(em"unexpected $classValueArg for the first argument to `createLocalJSClass`", classValueArg) jsdefn.JSObjectClass } diff --git a/tests/pos-with-compiler-cc/dotc/transform/sjs/ExplicitJSClasses.scala b/tests/pos-with-compiler-cc/dotc/transform/sjs/ExplicitJSClasses.scala index a7f6d3e7dea7..259ada490a8f 100644 --- a/tests/pos-with-compiler-cc/dotc/transform/sjs/ExplicitJSClasses.scala +++ b/tests/pos-with-compiler-cc/dotc/transform/sjs/ExplicitJSClasses.scala @@ -651,7 +651,7 @@ class ExplicitJSClasses extends MiniPhase with InfoTransformer { thisPhase => case typeRef: TypeRef => typeRef case _ => // This should not have passed the checks in PrepJSInterop - report.error(i"class type required but found $tpe0", tree) + report.error(em"class type required but found $tpe0", tree) jsdefn.JSObjectType } val cls = tpe.typeSymbol @@ -667,7 +667,7 @@ class ExplicitJSClasses extends MiniPhase with InfoTransformer { thisPhase => val jsclassAccessor = jsclassAccessorFor(cls) ref(NamedType(prefix, jsclassAccessor.name, jsclassAccessor.denot)) } else { - report.error(i"stable reference to a JS class required but $tpe found", tree) + report.error(em"stable reference to a JS class required but $tpe found", tree) ref(defn.Predef_undefined) } } else if (isLocalJSClass(cls)) { diff --git a/tests/pos-with-compiler-cc/dotc/transform/sjs/JUnitBootstrappers.scala b/tests/pos-with-compiler-cc/dotc/transform/sjs/JUnitBootstrappers.scala index 817a6c5afabc..b911d7dfab96 100644 --- a/tests/pos-with-compiler-cc/dotc/transform/sjs/JUnitBootstrappers.scala +++ b/tests/pos-with-compiler-cc/dotc/transform/sjs/JUnitBootstrappers.scala @@ -13,6 +13,7 @@ import Scopes._ import Symbols._ import StdNames._ import Types._ +import Decorators.em import dotty.tools.dotc.transform.MegaPhase._ @@ -238,7 +239,7 @@ class JUnitBootstrappers extends MiniPhase { case NamedArg(name, _) => name.show(using ctx) case other => other.show(using ctx) } - report.error(s"$shownName is an unsupported argument for the JUnit @Test annotation in this position", other.sourcePos) + report.error(em"$shownName is an unsupported argument for the JUnit @Test annotation in this position", other.sourcePos) None } } diff --git a/tests/pos-with-compiler-cc/dotc/transform/sjs/PrepJSExports.scala b/tests/pos-with-compiler-cc/dotc/transform/sjs/PrepJSExports.scala index b0de197635e9..25ab46712e70 100644 --- a/tests/pos-with-compiler-cc/dotc/transform/sjs/PrepJSExports.scala +++ b/tests/pos-with-compiler-cc/dotc/transform/sjs/PrepJSExports.scala @@ -189,7 +189,7 @@ object PrepJSExports { if (hasExplicitName) { annot.argumentConstantString(0).getOrElse { report.error( - s"The argument to ${annot.symbol.name} must be a literal string", + em"The argument to ${annot.symbol.name} must be a literal string", annot.arguments(0)) "dummy" } diff --git a/tests/pos-with-compiler-cc/dotc/transform/sjs/PrepJSInterop.scala b/tests/pos-with-compiler-cc/dotc/transform/sjs/PrepJSInterop.scala index e75769147f80..d934dc179989 100644 --- a/tests/pos-with-compiler-cc/dotc/transform/sjs/PrepJSInterop.scala +++ b/tests/pos-with-compiler-cc/dotc/transform/sjs/PrepJSInterop.scala @@ -248,9 +248,9 @@ class PrepJSInterop extends MacroTransform with IdentityDenotTransformer { thisP if (tpeSym.isJSType) { def reportError(reasonAndExplanation: String): Unit = { report.error( - "Using an anonymous function as a SAM for the JavaScript type " + - i"${tpeSym.fullName} is not allowed because " + - reasonAndExplanation, + em"Using an anonymous function as a SAM for the JavaScript type ${ + tpeSym.fullName + } is not allowed because $reasonAndExplanation", tree) } if (!tpeSym.is(Trait) || tpeSym.asClass.superClass != jsdefn.JSFunctionClass) { @@ -318,9 +318,9 @@ class PrepJSInterop extends MacroTransform with IdentityDenotTransformer { thisP nameArgs match { case List(Literal(Constant(s: String))) => if (s != "apply") - report.error(i"js.Dynamic.literal does not have a method named $s", tree) + report.error(em"js.Dynamic.literal does not have a method named $s", tree) case _ => - report.error(i"js.Dynamic.literal.${tree.symbol.name} may not be called directly", tree) + report.error(em"js.Dynamic.literal.${tree.symbol.name} may not be called directly", tree) } // TODO Warn for known duplicate property names @@ -381,7 +381,7 @@ class PrepJSInterop extends MacroTransform with IdentityDenotTransformer { thisP tpe.underlyingClassRef(refinementOK = false) match { case typeRef: TypeRef if typeRef.symbol.isOneOf(Trait | ModuleClass) => - report.error(i"non-trait class type required but $tpe found", tpeArg) + report.error(em"non-trait class type required but $tpe found", tpeArg) case _ => // an error was already reported above } @@ -440,7 +440,7 @@ class PrepJSInterop extends MacroTransform with IdentityDenotTransformer { thisP * which is never valid. */ report.error( - i"${sym.name} extends ${parentSym.fullName} which does not extend js.Any.", + em"${sym.name} extends ${parentSym.fullName} which does not extend js.Any.", classDef) } } @@ -502,8 +502,8 @@ class PrepJSInterop extends MacroTransform with IdentityDenotTransformer { thisP def emitOverrideError(msg: String): Unit = { report.error( - "error overriding %s;\n %s %s".format( - infoStringWithLocation(overridden), infoString(overriding), msg), + em"""error overriding ${infoStringWithLocation(overridden)}; + | ${infoString(overriding)} $msg""", errorPos) } @@ -559,7 +559,7 @@ class PrepJSInterop extends MacroTransform with IdentityDenotTransformer { thisP for (annot <- sym.annotations) { val annotSym = annot.symbol if (isJSNativeLoadingSpecAnnot(annotSym)) - report.error(i"Traits may not have an @${annotSym.name} annotation.", annot.tree) + report.error(em"Traits may not have an @${annotSym.name} annotation.", annot.tree) } } else { checkJSNativeLoadSpecOf(treePos, sym) @@ -571,7 +571,7 @@ class PrepJSInterop extends MacroTransform with IdentityDenotTransformer { thisP def checkGlobalRefName(globalRef: String): Unit = { if (!JSGlobalRef.isValidJSGlobalRefName(globalRef)) - report.error(s"The name of a JS global variable must be a valid JS identifier (got '$globalRef')", pos) + report.error(em"The name of a JS global variable must be a valid JS identifier (got '$globalRef')", pos) } if (enclosingOwner is OwnerKind.JSNative) { @@ -585,7 +585,7 @@ class PrepJSInterop extends MacroTransform with IdentityDenotTransformer { thisP for (annot <- sym.annotations) { val annotSym = annot.symbol if (isJSNativeLoadingSpecAnnot(annotSym)) - report.error(i"Nested JS classes and objects cannot have an @${annotSym.name} annotation.", annot.tree) + report.error(em"Nested JS classes and objects cannot have an @${annotSym.name} annotation.", annot.tree) } if (sym.owner.isStaticOwner) { @@ -731,7 +731,7 @@ class PrepJSInterop extends MacroTransform with IdentityDenotTransformer { thisP if (overriddenSymbols.hasNext) { val overridden = overriddenSymbols.next() val verb = if (overridden.is(Deferred)) "implement" else "override" - report.error(i"An @js.native member cannot $verb the inherited member ${overridden.fullName}", tree) + report.error(em"An @js.native member cannot $verb the inherited member ${overridden.fullName}", tree) } tree @@ -974,6 +974,8 @@ class PrepJSInterop extends MacroTransform with IdentityDenotTransformer { thisP tree.rhs match { case sel: Select if sel.symbol == jsdefn.JSPackage_native => // ok + case rhs: Ident if rhs.symbol == jsdefn.JSPackage_native => + // ok case _ => val pos = if (tree.rhs != EmptyTree) tree.rhs.srcPos else tree.srcPos report.error(s"$longKindStr may only call js.native.", pos) @@ -982,7 +984,7 @@ class PrepJSInterop extends MacroTransform with IdentityDenotTransformer { thisP // Check that the result type was explicitly specified // (This is stronger than Scala 2, which only warns, and only if it was inferred as Nothing.) if (tree.tpt.isInstanceOf[InferredTypeTree]) - report.error(i"The type of ${tree.name} must be explicitly specified because it is JS native.", tree) + report.error(em"The type of ${tree.name} must be explicitly specified because it is JS native.", tree) } private def checkJSNativeSpecificAnnotsOnNonJSNative(memberDef: MemberDef)(using Context): Unit = { @@ -1319,7 +1321,7 @@ object PrepJSInterop { for (annotation <- sym.annotations) { if (isCompilerAnnotation(annotation)) { report.error( - i"@${annotation.symbol.fullName} is for compiler internal use only. Do not use it yourself.", + em"@${annotation.symbol.fullName} is for compiler internal use only. Do not use it yourself.", annotation.tree) } } diff --git a/tests/pos-with-compiler-cc/dotc/typer/Applications.scala b/tests/pos-with-compiler-cc/dotc/typer/Applications.scala index fa237e316bd5..f0cccd0dbde3 100644 --- a/tests/pos-with-compiler-cc/dotc/typer/Applications.scala +++ b/tests/pos-with-compiler-cc/dotc/typer/Applications.scala @@ -23,7 +23,7 @@ import Inferencing._ import reporting._ import transform.TypeUtils._ import transform.SymUtils._ -import Nullables._ +import Nullables._, NullOpsDecorator.* import config.Feature import collection.mutable @@ -340,6 +340,12 @@ object Applications { val getter = findDefaultGetter(fn, n, testOnly) if getter.isEmpty then getter else spliceMeth(getter.withSpan(fn.span), fn) + + def retypeSignaturePolymorphicFn(fun: Tree, methType: Type)(using Context): Tree = + val sym1 = fun.symbol + val flags2 = sym1.flags | NonMember // ensures Select typing doesn't let TermRef#withPrefix revert the type + val sym2 = sym1.copy(info = methType, flags = flags2) // symbol not entered, to avoid overload resolution problems + fun.withType(sym2.termRef) } trait Applications extends Compatibility { @@ -478,7 +484,7 @@ trait Applications extends Compatibility { matchArgs(orderedArgs, methType.paramInfos, 0) case _ => if (methType.isError) ok = false - else fail(s"$methString does not take parameters".toMessage) + else fail(em"$methString does not take parameters") } /** The application was successful */ @@ -518,10 +524,10 @@ trait Applications extends Compatibility { else { // name not (or no longer) available for named arg def msg = if (methodType.paramNames contains aname) - s"parameter $aname of $methString is already instantiated" + em"parameter $aname of $methString is already instantiated" else - s"$methString does not have a parameter $aname" - fail(msg.toMessage, arg.asInstanceOf[Arg]) + em"$methString does not have a parameter $aname" + fail(msg, arg.asInstanceOf[Arg]) arg :: handleNamed(pnamesRest, args1, nameToArg, toDrop) } case arg :: args1 => @@ -563,7 +569,7 @@ trait Applications extends Compatibility { i"it is not the only argument to be passed to the corresponding repeated parameter $formal" else i"the corresponding parameter has type $formal which is not a repeated parameter type" - fail(em"Sequence argument type annotation `*` cannot be used here:\n$addendum".toMessage, arg) + fail(em"Sequence argument type annotation `*` cannot be used here:\n$addendum", arg) /** Add result of typing argument `arg` against parameter type `formal`. * @return The remaining formal parameter types. If the method is parameter-dependent @@ -647,10 +653,10 @@ trait Applications extends Compatibility { def msg = arg match case untpd.Tuple(Nil) if applyKind == ApplyKind.InfixTuple && funType.widen.isNullaryMethod => - i"can't supply unit value with infix notation because nullary $methString takes no arguments; use dotted invocation instead: (...).${methRef.name}()" + em"can't supply unit value with infix notation because nullary $methString takes no arguments; use dotted invocation instead: (...).${methRef.name}()" case _ => - i"too many arguments for $methString" - fail(msg.toMessage, arg) + em"too many arguments for $methString" + fail(msg, arg) case nil => } } @@ -936,6 +942,21 @@ trait Applications extends Compatibility { /** Type application where arguments come from prototype, and no implicits are inserted */ def simpleApply(fun1: Tree, proto: FunProto)(using Context): Tree = methPart(fun1).tpe match { + case funRef: TermRef if funRef.symbol.isSignaturePolymorphic => + // synthesize a method type based on the types at the call site. + // one can imagine the original signature-polymorphic method as + // being infinitely overloaded, with each individual overload only + // being brought into existence as needed + val originalResultType = funRef.symbol.info.resultType.stripNull + val resultType = + if !originalResultType.isRef(defn.ObjectClass) then originalResultType + else AvoidWildcardsMap()(proto.resultType.deepenProtoTrans) match + case SelectionProto(nme.asInstanceOf_, PolyProto(_, resTp), _, _) => resTp + case resTp if isFullyDefined(resTp, ForceDegree.all) => resTp + case _ => defn.ObjectType + val methType = MethodType(proto.typedArgs().map(_.tpe.widen), resultType) + val fun2 = Applications.retypeSignaturePolymorphicFn(fun1, methType) + simpleApply(fun2, proto) case funRef: TermRef => val app = ApplyTo(tree, fun1, funRef, proto, pt) convertNewGenericArray( @@ -1096,7 +1117,7 @@ trait Applications extends Compatibility { /** Overridden in ReTyper to handle primitive operations that can be generated after erasure */ protected def handleUnexpectedFunType(tree: untpd.Apply, fun: Tree)(using Context): Tree = if ctx.reporter.errorsReported then - throw TypeError(i"unexpected function type: ${methPart(fun).tpe}") + throw TypeError(em"unexpected function type: ${methPart(fun).tpe}") else throw Error(i"unexpected type.\n fun = $fun,\n methPart(fun) = ${methPart(fun)},\n methPart(fun).tpe = ${methPart(fun).tpe},\n tpe = ${fun.tpe}") @@ -1104,8 +1125,8 @@ trait Applications extends Compatibility { for (case arg @ NamedArg(id, argtpt) <- args) yield { if !Feature.namedTypeArgsEnabled then report.error( - i"""Named type arguments are experimental, - |they must be enabled with a `experimental.namedTypeArguments` language import or setting""", + em"""Named type arguments are experimental, + |they must be enabled with a `experimental.namedTypeArguments` language import or setting""", arg.srcPos) val argtpt1 = typedType(argtpt) cpy.NamedArg(arg)(id, argtpt1).withType(argtpt1.tpe) @@ -1396,7 +1417,7 @@ trait Applications extends Compatibility { case Apply(Apply(unapply, `dummyArg` :: Nil), args2) => assert(args2.nonEmpty); res ++= args2 case Apply(unapply, `dummyArg` :: Nil) => case Inlined(u, _, _) => loop(u) - case DynamicUnapply(_) => report.error("Structural unapply is not supported", unapplyFn.srcPos) + case DynamicUnapply(_) => report.error(em"Structural unapply is not supported", unapplyFn.srcPos) case Apply(fn, args) => assert(args.nonEmpty); loop(fn); res ++= args case _ => ().assertingErrorsReported } @@ -1501,11 +1522,17 @@ trait Applications extends Compatibility { } /** Drop any leading implicit parameter sections */ - def stripImplicit(tp: Type)(using Context): Type = tp match { + def stripImplicit(tp: Type, wildcardOnly: Boolean = false)(using Context): Type = tp match { case mt: MethodType if mt.isImplicitMethod => - stripImplicit(resultTypeApprox(mt)) + stripImplicit(resultTypeApprox(mt, wildcardOnly)) case pt: PolyType => - pt.derivedLambdaType(pt.paramNames, pt.paramInfos, stripImplicit(pt.resultType)).asInstanceOf[PolyType].flatten + pt.derivedLambdaType(pt.paramNames, pt.paramInfos, + stripImplicit(pt.resultType, wildcardOnly = true)) + // can't use TypeParamRefs for parameter references in `resultTypeApprox` + // since their bounds can refer to type parameters in `pt` that are not + // bound by the constraint. This can lead to hygiene violations if subsequently + // `pt` itself is added to the constraint. Test case is run/enrich-gentraversable.scala. + .asInstanceOf[PolyType].flatten case _ => tp } diff --git a/tests/pos-with-compiler-cc/dotc/typer/Checking.scala b/tests/pos-with-compiler-cc/dotc/typer/Checking.scala index 8d9687cbb21d..d8095130e206 100644 --- a/tests/pos-with-compiler-cc/dotc/typer/Checking.scala +++ b/tests/pos-with-compiler-cc/dotc/typer/Checking.scala @@ -473,11 +473,11 @@ object Checking { def checkWithDeferred(flag: FlagSet) = if (sym.isOneOf(flag)) fail(AbstractMemberMayNotHaveModifier(sym, flag)) - def checkNoConflict(flag1: FlagSet, flag2: FlagSet, msg: -> String) = - if (sym.isAllOf(flag1 | flag2)) fail(msg.toMessage) + def checkNoConflict(flag1: FlagSet, flag2: FlagSet, msg: Message) = + if (sym.isAllOf(flag1 | flag2)) fail(msg) def checkCombination(flag1: FlagSet, flag2: FlagSet) = if sym.isAllOf(flag1 | flag2) then - fail(i"illegal combination of modifiers: `${flag1.flagsString}` and `${flag2.flagsString}` for: $sym".toMessage) + fail(em"illegal combination of modifiers: `${flag1.flagsString}` and `${flag2.flagsString}` for: $sym") def checkApplicable(flag: Flag, ok: Boolean) = if sym.is(flag, butNot = Synthetic) && !ok then fail(ModifierNotAllowedForDefinition(flag)) @@ -497,15 +497,15 @@ object Checking { } if sym.is(Transparent) then if sym.isType then - if !sym.isExtensibleClass then fail(em"`transparent` can only be used for extensible classes and traits".toMessage) + if !sym.isExtensibleClass then fail(em"`transparent` can only be used for extensible classes and traits") else - if !sym.isInlineMethod then fail(em"`transparent` can only be used for inline methods".toMessage) + if !sym.isInlineMethod then fail(em"`transparent` can only be used for inline methods") if (!sym.isClass && sym.is(Abstract)) fail(OnlyClassesCanBeAbstract(sym)) // note: this is not covered by the next test since terms can be abstract (which is a dual-mode flag) // but they can never be one of ClassOnlyFlags if !sym.isClass && sym.isOneOf(ClassOnlyFlags) then - fail(em"only classes can be ${(sym.flags & ClassOnlyFlags).flagsString}".toMessage) + fail(em"only classes can be ${(sym.flags & ClassOnlyFlags).flagsString}") if (sym.is(AbsOverride) && !sym.owner.is(Trait)) fail(AbstractOverrideOnlyInTraits(sym)) if sym.is(Trait) then @@ -522,7 +522,7 @@ object Checking { if !sym.isOneOf(Method | ModuleVal) then fail(TailrecNotApplicable(sym)) else if sym.is(Inline) then - fail("Inline methods cannot be @tailrec".toMessage) + fail(em"Inline methods cannot be @tailrec") if sym.hasAnnotation(defn.TargetNameAnnot) && sym.isClass && sym.isTopLevelClass then fail(TargetNameOnTopLevelClass(sym)) if (sym.hasAnnotation(defn.NativeAnnot)) { @@ -541,7 +541,7 @@ object Checking { fail(CannotExtendAnyVal(sym)) if (sym.isConstructor && !sym.isPrimaryConstructor && sym.owner.is(Trait, butNot = JavaDefined)) val addendum = if ctx.settings.Ydebug.value then s" ${sym.owner.flagsString}" else "" - fail(s"Traits cannot have secondary constructors$addendum".toMessage) + fail(em"Traits cannot have secondary constructors$addendum") checkApplicable(Inline, sym.isTerm && !sym.isOneOf(Mutable | Module)) checkApplicable(Lazy, !sym.isOneOf(Method | Mutable)) if (sym.isType && !sym.isOneOf(Deferred | JavaDefined)) @@ -562,7 +562,7 @@ object Checking { // The issue with `erased inline` is that the erased semantics get lost // as the code is inlined and the reference is removed before the erased usage check. checkCombination(Erased, Inline) - checkNoConflict(Lazy, ParamAccessor, s"parameter may not be `lazy`") + checkNoConflict(Lazy, ParamAccessor, em"parameter may not be `lazy`") } /** Check for illegal or redundant modifiers on modules. This is done separately @@ -601,7 +601,7 @@ object Checking { */ def checkNoPrivateLeaks(sym: Symbol)(using Context): Type = { class NotPrivate extends TypeMap { - var errors: List[() -> String] = Nil + var errors: List[Message] = Nil private var inCaptureSet: Boolean = false def accessBoundary(sym: Symbol): Symbol = @@ -633,7 +633,7 @@ object Checking { var tp1 = if (isLeaked(tp.symbol)) { errors = - (() => em"non-private ${sym.showLocated} refers to private ${tp.symbol}\nin its type signature ${sym.info}") + em"non-private ${sym.showLocated} refers to private ${tp.symbol}\nin its type signature ${sym.info}" :: errors tp } @@ -674,7 +674,7 @@ object Checking { } val notPrivate = new NotPrivate val info = notPrivate(sym.info) - notPrivate.errors.foreach(error => report.errorOrMigrationWarning(error(), sym.srcPos, from = `3.0`)) + notPrivate.errors.foreach(report.errorOrMigrationWarning(_, sym.srcPos, from = `3.0`)) info } @@ -815,7 +815,7 @@ trait Checking { def checkRealizableBounds(cls: Symbol, pos: SrcPos)(using Context): Unit = { val rstatus = boundsRealizability(cls.thisType) if (rstatus ne Realizable) - report.error(ex"$cls cannot be instantiated since it${rstatus.msg}", pos) + report.error(em"$cls cannot be instantiated since it${rstatus.msg}", pos) } /** Check that pattern `pat` is irrefutable for scrutinee type `sel.tpe`. @@ -836,7 +836,7 @@ trait Checking { var reportedPt = pt.dropAnnot(defn.UncheckedAnnot) if !pat.tpe.isSingleton then reportedPt = reportedPt.widen val problem = if pat.tpe <:< reportedPt then "is more specialized than" else "does not match" - ex"pattern's type ${pat.tpe} $problem the right hand side expression's type $reportedPt" + em"pattern's type ${pat.tpe} $problem the right hand side expression's type $reportedPt" case RefutableExtractor => val extractor = val UnApply(fn, _, _) = pat: @unchecked @@ -864,10 +864,11 @@ trait Checking { else pat.srcPos def rewriteMsg = Message.rewriteNotice("This patch", `3.2-migration`) report.gradualErrorOrMigrationWarning( - em"""$message - | - |If $usage is intentional, this can be communicated by $fix, - |which $addendum.$rewriteMsg""", + message.append( + i"""| + | + |If $usage is intentional, this can be communicated by $fix, + |which $addendum.$rewriteMsg"""), pos, warnFrom = `3.2`, errorFrom = `future`) false } @@ -936,8 +937,8 @@ trait Checking { // we restrict wildcard export from package as incremental compilation does not yet // register a dependency on "all members of a package" - see https://github.com/sbt/zinc/issues/226 report.error( - em"Implementation restriction: ${path.tpe.classSymbol} is not a valid prefix " + - "for a wildcard export, as it is a package.", path.srcPos) + em"Implementation restriction: ${path.tpe.classSymbol} is not a valid prefix for a wildcard export, as it is a package", + path.srcPos) /** Check that module `sym` does not clash with a class of the same name * that is concurrently compiled in another source file. @@ -980,14 +981,15 @@ trait Checking { sym.srcPos) /** If `tree` is an application of a new-style implicit conversion (using the apply - * method of a `scala.Conversion` instance), check that implicit conversions are - * enabled. + * method of a `scala.Conversion` instance), check that the expected type is + * a convertible formal parameter type or that implicit conversions are enabled. */ - def checkImplicitConversionUseOK(tree: Tree)(using Context): Unit = + def checkImplicitConversionUseOK(tree: Tree, expected: Type)(using Context): Unit = val sym = tree.symbol if sym.name == nme.apply && sym.owner.derivesFrom(defn.ConversionClass) && !sym.info.isErroneous + && !expected.isConvertibleParam then def conv = methPart(tree) match case Select(qual, _) => qual.symbol.orElse(sym.owner) @@ -1023,8 +1025,8 @@ trait Checking { ("method", (n: Name) => s"method syntax .$n(...)") def rewriteMsg = Message.rewriteNotice("The latter", options = "-deprecation") report.deprecationWarning( - i"""Alphanumeric $kind $name is not declared ${hlAsKeyword("infix")}; it should not be used as infix operator. - |Instead, use ${alternative(name)} or backticked identifier `$name`.$rewriteMsg""", + em"""Alphanumeric $kind $name is not declared ${hlAsKeyword("infix")}; it should not be used as infix operator. + |Instead, use ${alternative(name)} or backticked identifier `$name`.$rewriteMsg""", tree.op.srcPos) if (ctx.settings.deprecation.value) { patch(Span(tree.op.span.start, tree.op.span.start), "`") @@ -1050,14 +1052,14 @@ trait Checking { def checkFeasibleParent(tp: Type, pos: SrcPos, where: -> String = "")(using Context): Type = { def checkGoodBounds(tp: Type) = tp match { case tp @ TypeBounds(lo, hi) if !(lo <:< hi) => - report.error(ex"no type exists between low bound $lo and high bound $hi$where", pos) + report.error(em"no type exists between low bound $lo and high bound $hi$where", pos) TypeBounds(hi, hi) case _ => tp } tp match { case tp @ AndType(tp1, tp2) => - report.error(s"conflicting type arguments$where", pos) + report.error(em"conflicting type arguments$where", pos) tp1 case tp @ AppliedType(tycon, args) => tp.derivedAppliedType(tycon, args.mapConserve(checkGoodBounds)) @@ -1112,11 +1114,11 @@ trait Checking { if (!ctx.isAfterTyper) { val called = call.tpe.classSymbol if (called.is(JavaAnnotation)) - report.error(i"${called.name} must appear without any argument to be a valid class parent because it is a Java annotation", call.srcPos) + report.error(em"${called.name} must appear without any argument to be a valid class parent because it is a Java annotation", call.srcPos) if (caller.is(Trait)) - report.error(i"$caller may not call constructor of $called", call.srcPos) + report.error(em"$caller may not call constructor of $called", call.srcPos) else if (called.is(Trait) && !caller.mixins.contains(called)) - report.error(i"""$called is already implemented by super${caller.superClass}, + report.error(em"""$called is already implemented by super${caller.superClass}, |its constructor cannot be called again""", call.srcPos) // Check that constructor call is of the form _.(args1)...(argsN). @@ -1125,7 +1127,7 @@ trait Checking { case Apply(fn, _) => checkLegalConstructorCall(fn, tree, "") case TypeApply(fn, _) => checkLegalConstructorCall(fn, tree, "type ") case Select(_, nme.CONSTRUCTOR) => // ok - case _ => report.error(s"too many ${kind}arguments in parent constructor", encl.srcPos) + case _ => report.error(em"too many ${kind}arguments in parent constructor", encl.srcPos) } call match { case Apply(fn, _) => checkLegalConstructorCall(fn, call, "") @@ -1175,7 +1177,7 @@ trait Checking { parent match { case parent: ClassSymbol => if (parent.is(Case)) - report.error(ex"""case $caseCls has case ancestor $parent, but case-to-case inheritance is prohibited. + report.error(em"""case $caseCls has case ancestor $parent, but case-to-case inheritance is prohibited. |To overcome this limitation, use extractors to pattern match on non-leaf nodes.""", pos) else checkCaseInheritance(parent.superClass, caseCls, pos) case _ => @@ -1189,7 +1191,7 @@ trait Checking { val check = new TreeTraverser { def traverse(tree: Tree)(using Context) = tree match { case id: Ident if vparams.exists(_.symbol == id.symbol) => - report.error("illegal forward reference to method parameter", id.srcPos) + report.error(em"illegal forward reference to method parameter", id.srcPos) case _ => traverseChildren(tree) } @@ -1232,7 +1234,7 @@ trait Checking { if (t.span.isSourceDerived && owner == badOwner) t match { case t: RefTree if allowed(t.name, checkedSym) => - case _ => report.error(i"illegal reference to $checkedSym from $where", t.srcPos) + case _ => report.error(em"illegal reference to $checkedSym from $where", t.srcPos) } val sym = t.symbol t match { @@ -1349,7 +1351,7 @@ trait Checking { def ensureParentDerivesFrom(enumCase: Symbol)(using Context) = val enumCls = enumCase.owner.linkedClass if !firstParent.derivesFrom(enumCls) then - report.error(i"enum case does not extend its enum $enumCls", enumCase.srcPos) + report.error(em"enum case does not extend its enum $enumCls", enumCase.srcPos) cls.info match case info: ClassInfo => cls.info = info.derivedClassInfo(declaredParents = enumCls.typeRefApplied :: info.declaredParents) @@ -1537,7 +1539,7 @@ trait NoChecking extends ReChecking { override def checkStable(tp: Type, pos: SrcPos, kind: String)(using Context): Unit = () override def checkClassType(tp: Type, pos: SrcPos, traitReq: Boolean, stablePrefixReq: Boolean)(using Context): Type = tp override def checkImplicitConversionDefOK(sym: Symbol)(using Context): Unit = () - override def checkImplicitConversionUseOK(tree: Tree)(using Context): Unit = () + override def checkImplicitConversionUseOK(tree: Tree, expected: Type)(using Context): Unit = () override def checkFeasibleParent(tp: Type, pos: SrcPos, where: -> String = "")(using Context): Type = tp override def checkAnnotArgs(tree: Tree)(using Context): tree.type = tree override def checkNoTargetNameConflict(stats: List[Tree])(using Context): Unit = () diff --git a/tests/pos-with-compiler-cc/dotc/typer/CrossVersionChecks.scala b/tests/pos-with-compiler-cc/dotc/typer/CrossVersionChecks.scala index 746b01c934a3..ef9599be551c 100644 --- a/tests/pos-with-compiler-cc/dotc/typer/CrossVersionChecks.scala +++ b/tests/pos-with-compiler-cc/dotc/typer/CrossVersionChecks.scala @@ -67,7 +67,7 @@ class CrossVersionChecks extends MiniPhase: if !skipWarning then val msg = annot.argumentConstant(0).map(": " + _.stringValue).getOrElse("") val since = annot.argumentConstant(1).map(" since " + _.stringValue).getOrElse("") - report.deprecationWarning(s"${sym.showLocated} is deprecated${since}${msg}", pos) + report.deprecationWarning(em"${sym.showLocated} is deprecated${since}${msg}", pos) private def checkExperimentalSignature(sym: Symbol, pos: SrcPos)(using Context): Unit = class Checker extends TypeTraverser: @@ -110,8 +110,9 @@ class CrossVersionChecks extends MiniPhase: !sym.isDeprecated && !sym.is(Deferred)) if (!concrOvers.isEmpty) report.deprecationWarning( - symbol.toString + " overrides concrete, non-deprecated symbol(s):" + - concrOvers.map(_.name).mkString(" ", ", ", ""), tree.srcPos) + em"""$symbol overrides concrete, non-deprecated definition(s): + | ${concrOvers.map(_.name).mkString(", ")}""", + tree.srcPos) } } diff --git a/tests/pos-with-compiler-cc/dotc/typer/Deriving.scala b/tests/pos-with-compiler-cc/dotc/typer/Deriving.scala index d2165a5ca8c5..8fdc468780ba 100644 --- a/tests/pos-with-compiler-cc/dotc/typer/Deriving.scala +++ b/tests/pos-with-compiler-cc/dotc/typer/Deriving.scala @@ -44,7 +44,7 @@ trait Deriving { private def addDerivedInstance(clsName: Name, info: Type, pos: SrcPos): Unit = { val instanceName = "derived$".concat(clsName) if (ctx.denotNamed(instanceName).exists) - report.error(i"duplicate type class derivation for $clsName", pos) + report.error(em"duplicate type class derivation for $clsName", pos) else // If we set the Synthetic flag here widenGiven will widen too far and the // derived instance will have too low a priority to be selected over a freshly @@ -90,7 +90,7 @@ trait Deriving { xs.corresponds(ys)((x, y) => x.paramInfo.hasSameKindAs(y.paramInfo)) def cannotBeUnified = - report.error(i"${cls.name} cannot be unified with the type argument of ${typeClass.name}", derived.srcPos) + report.error(em"${cls.name} cannot be unified with the type argument of ${typeClass.name}", derived.srcPos) def addInstance(derivedParams: List[TypeSymbol], evidenceParamInfos: List[List[Type]], instanceTypes: List[Type]): Unit = { val resultType = typeClassType.appliedTo(instanceTypes) @@ -252,7 +252,7 @@ trait Deriving { if (typeClassArity == 1) deriveSingleParameter else if (typeClass == defn.CanEqualClass) deriveCanEqual else if (typeClassArity == 0) - report.error(i"type ${typeClass.name} in derives clause of ${cls.name} has no type parameters", derived.srcPos) + report.error(em"type ${typeClass.name} in derives clause of ${cls.name} has no type parameters", derived.srcPos) else cannotBeUnified } diff --git a/tests/pos-with-compiler-cc/dotc/typer/Docstrings.scala b/tests/pos-with-compiler-cc/dotc/typer/Docstrings.scala index 5fefd355d7d8..d819528ff556 100644 --- a/tests/pos-with-compiler-cc/dotc/typer/Docstrings.scala +++ b/tests/pos-with-compiler-cc/dotc/typer/Docstrings.scala @@ -37,7 +37,7 @@ object Docstrings { case List(df: tpd.DefDef) => usecase.typed(df) case _ => - report.error("`@usecase` was not a valid definition", ctx.source.atSpan(usecase.codePos)) + report.error(em"`@usecase` was not a valid definition", ctx.source.atSpan(usecase.codePos)) usecase } } diff --git a/tests/pos-with-compiler-cc/dotc/typer/ErrorReporting.scala b/tests/pos-with-compiler-cc/dotc/typer/ErrorReporting.scala index c63e8e900f67..16f39131372b 100644 --- a/tests/pos-with-compiler-cc/dotc/typer/ErrorReporting.scala +++ b/tests/pos-with-compiler-cc/dotc/typer/ErrorReporting.scala @@ -10,11 +10,8 @@ import Trees._ import NameOps._ import util.SrcPos import config.Feature -import java.util.regex.Matcher.quoteReplacement import reporting._ import collection.mutable - -import scala.util.matching.Regex import language.experimental.pureFunctions object ErrorReporting { @@ -88,18 +85,18 @@ object ErrorReporting { def expectedTypeStr(tp: Type): String = tp match { case tp: PolyProto => - em"type arguments [${tp.targs.tpes}%, %] and ${expectedTypeStr(revealDeepenedArgs(tp.resultType))}" + i"type arguments [${tp.targs.tpes}%, %] and ${expectedTypeStr(revealDeepenedArgs(tp.resultType))}" case tp: FunProto => def argStr(tp: FunProto): String = val result = revealDeepenedArgs(tp.resultType) match { case restp: FunProto => argStr(restp) case _: WildcardType | _: IgnoredProto => "" - case tp => em" and expected result type $tp" + case tp => i" and expected result type $tp" } - em"(${tp.typedArgs().tpes}%, %)$result" + i"(${tp.typedArgs().tpes}%, %)$result" s"arguments ${argStr(tp)}" case _ => - em"expected type $tp" + i"expected type $tp" } def anonymousTypeMemberStr(tpe: Type): String = { @@ -108,12 +105,12 @@ object ErrorReporting { case _: MethodOrPoly => "method" case _ => "value of type" } - em"$kind $tpe" + i"$kind $tpe" } def overloadedAltsStr(alts: List[SingleDenotation]): String = - em"overloaded alternatives of ${denotStr(alts.head)} with types\n" + - em" ${alts map (_.info)}%\n %" + i"""overloaded alternatives of ${denotStr(alts.head)} with types + | ${alts map (_.info)}%\n %""" def denotStr(denot: Denotation): String = if (denot.isOverloaded) overloadedAltsStr(denot.alternatives) @@ -131,6 +128,23 @@ object ErrorReporting { case _ => anonymousTypeMemberStr(tp) } + /** Explain info of symbol `sym` as a member of class `base`. + * @param showLocation if true also show sym's location. + */ + def infoString(sym: Symbol, base: Type, showLocation: Boolean): String = + val sym1 = sym.underlyingSymbol + def info = base.memberInfo(sym1) + val infoStr = + if sym1.isAliasType then i", which equals ${info.bounds.hi}" + else if sym1.isAbstractOrParamType && info != TypeBounds.empty then i" with bounds$info" + else if sym1.is(Module) then "" + else if sym1.isTerm then i" of type $info" + else "" + i"${if showLocation then sym1.showLocated else sym1}$infoStr" + + def infoStringWithLocation(sym: Symbol, base: Type) = + infoString(sym, base, showLocation = true) + def exprStr(tree: Tree): String = refStr(tree.tpe) def takesNoParamsStr(tree: Tree, kind: String): String = @@ -264,195 +278,3 @@ object ErrorReporting { def err(using Context): Errors = new Errors } - -class ImplicitSearchError( - arg: tpd.Tree, - pt: Type, - where: String, - paramSymWithMethodCallTree: Option[(Symbol, tpd.Tree)] = None, - ignoredInstanceNormalImport: -> Option[SearchSuccess], - importSuggestionAddendum: -> String -)(using ctx: Context) { - - def missingArgMsg = arg.tpe match { - case ambi: AmbiguousImplicits => - (ambi.alt1, ambi.alt2) match { - case (alt @ AmbiguousImplicitMsg(msg), _) => - userDefinedAmbiguousImplicitMsg(alt, msg) - case (_, alt @ AmbiguousImplicitMsg(msg)) => - userDefinedAmbiguousImplicitMsg(alt, msg) - case _ => - defaultAmbiguousImplicitMsg(ambi) - } - case ambi @ TooUnspecific(target) => - ex"""No implicit search was attempted${location("for")} - |since the expected type $target is not specific enough""" - case _ => - val shortMessage = userDefinedImplicitNotFoundParamMessage - .orElse(userDefinedImplicitNotFoundTypeMessage) - .getOrElse(defaultImplicitNotFoundMessage) - formatMsg(shortMessage)() - ++ hiddenImplicitsAddendum - ++ ErrorReporting.matchReductionAddendum(pt) - } - - private def formatMsg(shortForm: String)(headline: String = shortForm) = arg match - case arg: Trees.SearchFailureIdent[?] => - arg.tpe match - case _: NoMatchingImplicits => headline - case tpe: SearchFailureType => - i"$headline. ${tpe.explanation}" - case _ => headline - case _ => - arg.tpe match - case tpe: SearchFailureType => - val original = arg match - case Inlined(call, _, _) => call - case _ => arg - i"""$headline. - |I found: - | - | ${original.show.replace("\n", "\n ")} - | - |But ${tpe.explanation}.""" - case _ => headline - - /** Format `raw` implicitNotFound or implicitAmbiguous argument, replacing - * all occurrences of `${X}` where `X` is in `paramNames` with the - * corresponding shown type in `args`. - */ - private def userDefinedErrorString(raw: String, paramNames: List[String], args: List[Type]): String = { - def translate(name: String): Option[String] = { - val idx = paramNames.indexOf(name) - if (idx >= 0) Some(ex"${args(idx)}") else None - } - - """\$\{\s*([^}\s]+)\s*\}""".r.replaceAllIn(raw, (_: Regex.Match) match { - case Regex.Groups(v) => quoteReplacement(translate(v).getOrElse("")).nn - }) - } - - /** Extract a user defined error message from a symbol `sym` - * with an annotation matching the given class symbol `cls`. - */ - private def userDefinedMsg(sym: Symbol, cls: Symbol) = for { - ann <- sym.getAnnotation(cls) - msg <- ann.argumentConstantString(0) - } yield msg - - private def location(preposition: String) = if (where.isEmpty) "" else s" $preposition $where" - - private def defaultAmbiguousImplicitMsg(ambi: AmbiguousImplicits) = - s"Ambiguous given instances: ${ambi.explanation}${location("of")}" - - private def defaultImplicitNotFoundMessage = - ex"No given instance of type $pt was found${location("for")}" - - /** Construct a custom error message given an ambiguous implicit - * candidate `alt` and a user defined message `raw`. - */ - private def userDefinedAmbiguousImplicitMsg(alt: SearchSuccess, raw: String) = { - val params = alt.ref.underlying match { - case p: PolyType => p.paramNames.map(_.toString) - case _ => Nil - } - def resolveTypes(targs: List[tpd.Tree])(using Context) = - targs.map(a => Inferencing.fullyDefinedType(a.tpe, "type argument", a.srcPos)) - - // We can extract type arguments from: - // - a function call: - // @implicitAmbiguous("msg A=${A}") - // implicit def f[A](): String = ... - // implicitly[String] // found: f[Any]() - // - // - an eta-expanded function: - // @implicitAmbiguous("msg A=${A}") - // implicit def f[A](x: Int): String = ... - // implicitly[Int => String] // found: x => f[Any](x) - - val call = tpd.closureBody(alt.tree) // the tree itself if not a closure - val targs = tpd.typeArgss(call).flatten - val args = resolveTypes(targs)(using ctx.fresh.setTyperState(alt.tstate)) - userDefinedErrorString(raw, params, args) - } - - /** @param rawMsg Message template with variables, e.g. "Variable A is ${A}" - * @param sym Symbol of the annotated type or of the method whose parameter was annotated - * @param substituteType Function substituting specific types for abstract types associated with variables, e.g A -> Int - */ - private def formatAnnotationMessage(rawMsg: String, sym: Symbol, substituteType: Type => Type): String = { - val substitutableTypesSymbols = ErrorReporting.substitutableTypeSymbolsInScope(sym) - - userDefinedErrorString( - rawMsg, - paramNames = substitutableTypesSymbols.map(_.name.unexpandedName.toString), - args = substitutableTypesSymbols.map(_.typeRef).map(substituteType) - ) - } - - /** Extracting the message from a method parameter, e.g. in - * - * trait Foo - * - * def foo(implicit @annotation.implicitNotFound("Foo is missing") foo: Foo): Any = ??? - */ - private def userDefinedImplicitNotFoundParamMessage: Option[String] = paramSymWithMethodCallTree.flatMap { (sym, applTree) => - userDefinedMsg(sym, defn.ImplicitNotFoundAnnot).map { rawMsg => - val fn = tpd.funPart(applTree) - val targs = tpd.typeArgss(applTree).flatten - val methodOwner = fn.symbol.owner - val methodOwnerType = tpd.qualifier(fn).tpe - val methodTypeParams = fn.symbol.paramSymss.flatten.filter(_.isType) - val methodTypeArgs = targs.map(_.tpe) - val substituteType = (_: Type).asSeenFrom(methodOwnerType, methodOwner).subst(methodTypeParams, methodTypeArgs) - formatAnnotationMessage(rawMsg, sym.owner, substituteType) - } - } - - /** Extracting the message from a type, e.g. in - * - * @annotation.implicitNotFound("Foo is missing") - * trait Foo - * - * def foo(implicit foo: Foo): Any = ??? - */ - private def userDefinedImplicitNotFoundTypeMessage: Option[String] = - def recur(tp: Type): Option[String] = tp match - case tp: TypeRef => - val sym = tp.symbol - userDefinedImplicitNotFoundTypeMessage(sym).orElse(recur(tp.info)) - case tp: ClassInfo => - tp.baseClasses.iterator - .map(userDefinedImplicitNotFoundTypeMessage) - .find(_.isDefined).flatten - case tp: TypeProxy => - recur(tp.superType) - case tp: AndType => - recur(tp.tp1).orElse(recur(tp.tp2)) - case _ => - None - recur(pt) - - private def userDefinedImplicitNotFoundTypeMessage(sym: Symbol): Option[String] = - for - rawMsg <- userDefinedMsg(sym, defn.ImplicitNotFoundAnnot) - if Feature.migrateTo3 || sym != defn.Function1 - // Don't inherit "No implicit view available..." message if subtypes of Function1 are not treated as implicit conversions anymore - yield - val substituteType = (_: Type).asSeenFrom(pt, sym) - formatAnnotationMessage(rawMsg, sym, substituteType) - - private def hiddenImplicitsAddendum: String = - def hiddenImplicitNote(s: SearchSuccess) = - em"\n\nNote: ${s.ref.symbol.showLocated} was not considered because it was not imported with `import given`." - - val normalImports = ignoredInstanceNormalImport.map(hiddenImplicitNote) - - normalImports.getOrElse(importSuggestionAddendum) - end hiddenImplicitsAddendum - - private object AmbiguousImplicitMsg { - def unapply(search: SearchSuccess): Option[String] = - userDefinedMsg(search.ref.symbol, defn.ImplicitAmbiguousAnnot) - } -} diff --git a/tests/pos-with-compiler-cc/dotc/typer/Implicits.scala b/tests/pos-with-compiler-cc/dotc/typer/Implicits.scala index 5200dd73a313..a421ea8f26d1 100644 --- a/tests/pos-with-compiler-cc/dotc/typer/Implicits.scala +++ b/tests/pos-with-compiler-cc/dotc/typer/Implicits.scala @@ -435,20 +435,15 @@ object Implicits: final protected def qualify(using Context): String = expectedType match { case SelectionProto(name, mproto, _, _) if !argument.isEmpty => - em"provide an extension method `$name` on ${argument.tpe}" + i"provide an extension method `$name` on ${argument.tpe}" case NoType => - if (argument.isEmpty) em"match expected type" - else em"convert from ${argument.tpe} to expected type" + if (argument.isEmpty) i"match expected type" + else i"convert from ${argument.tpe} to expected type" case _ => - if (argument.isEmpty) em"match type ${clarify(expectedType)}" - else em"convert from ${argument.tpe} to ${clarify(expectedType)}" + if (argument.isEmpty) i"match type ${clarify(expectedType)}" + else i"convert from ${argument.tpe} to ${clarify(expectedType)}" } - /** An explanation of the cause of the failure as a string */ - def explanation(using Context): String - - def msg(using Context): Message = explanation.toMessage - /** If search was for an implicit conversion, a note describing the failure * in more detail - this is either empty or starts with a '\n' */ @@ -488,8 +483,9 @@ object Implicits: map(tp) } - def explanation(using Context): String = + def msg(using Context): Message = em"no implicit values were found that $qualify" + override def toString = s"NoMatchingImplicits($expectedType, $argument)" } @@ -509,20 +505,20 @@ object Implicits: i""" |Note that implicit conversions were not tried because the result of an implicit conversion |must be more specific than $target""" - override def explanation(using Context) = - i"""${super.explanation}. - |The expected type $target is not specific enough, so no search was attempted""" + + override def msg(using Context) = + super.msg.append("\nThe expected type $target is not specific enough, so no search was attempted") override def toString = s"TooUnspecific" /** An ambiguous implicits failure */ class AmbiguousImplicits(val alt1: SearchSuccess, val alt2: SearchSuccess, val expectedType: Type, val argument: Tree) extends SearchFailureType { - def explanation(using Context): String = + def msg(using Context): Message = var str1 = err.refStr(alt1.ref) var str2 = err.refStr(alt2.ref) if str1 == str2 then str1 = ctx.printer.toTextRef(alt1.ref).show str2 = ctx.printer.toTextRef(alt2.ref).show - em"both $str1 and $str2 $qualify" + em"both $str1 and $str2 $qualify".withoutDisambiguation() override def whyNoConversion(using Context): String = if !argument.isEmpty && argument.tpe.widen.isRef(defn.NothingClass) then "" @@ -536,21 +532,21 @@ object Implicits: class MismatchedImplicit(ref: TermRef, val expectedType: Type, val argument: Tree) extends SearchFailureType { - def explanation(using Context): String = + def msg(using Context): Message = em"${err.refStr(ref)} does not $qualify" } class DivergingImplicit(ref: TermRef, val expectedType: Type, val argument: Tree) extends SearchFailureType { - def explanation(using Context): String = + def msg(using Context): Message = em"${err.refStr(ref)} produces a diverging implicit search when trying to $qualify" } /** A search failure type for attempted ill-typed extension method calls */ class FailedExtension(extApp: Tree, val expectedType: Type, val whyFailed: Message) extends SearchFailureType: def argument = EmptyTree - def explanation(using Context) = em"$extApp does not $qualify" + def msg(using Context) = em"$extApp does not $qualify" /** A search failure type for aborted searches of extension methods, typically * because of a cyclic reference or similar. @@ -558,7 +554,6 @@ object Implicits: class NestedFailure(_msg: Message, val expectedType: Type) extends SearchFailureType: def argument = EmptyTree override def msg(using Context) = _msg - def explanation(using Context) = msg.toString /** A search failure type for failed synthesis of terms for special types */ class SynthesisFailure(reasons: List[String], val expectedType: Type) extends SearchFailureType: @@ -570,7 +565,7 @@ object Implicits: else reasons.mkString(" ", "", "") - def explanation(using Context) = em"Failed to synthesize an instance of type ${clarify(expectedType)}:${formatReasons}" + def msg(using Context) = em"Failed to synthesize an instance of type ${clarify(expectedType)}:${formatReasons}" end Implicits @@ -851,7 +846,7 @@ trait Implicits: inferred match { case SearchSuccess(_, ref, _, false) if isOldStyleFunctionConversion(ref.underlying) => report.migrationWarning( - i"The conversion ${ref} will not be applied implicitly here in Scala 3 because only implicit methods and instances of Conversion class will continue to work as implicit views.", + em"The conversion ${ref} will not be applied implicitly here in Scala 3 because only implicit methods and instances of Conversion class will continue to work as implicit views.", from ) case _ => @@ -905,7 +900,7 @@ trait Implicits: pt: Type, where: String, paramSymWithMethodCallTree: Option[(Symbol, Tree)] = None - )(using Context): String = { + )(using Context): Message = { def findHiddenImplicitsCtx(c: Context): Context = if c == NoContext then c else c.freshOver(findHiddenImplicitsCtx(c.outer)).addMode(Mode.FindHiddenImplicits) @@ -928,8 +923,7 @@ trait Implicits: // example where searching for a nested type causes an infinite loop. None - val error = new ImplicitSearchError(arg, pt, where, paramSymWithMethodCallTree, ignoredInstanceNormalImport, importSuggestionAddendum(pt)) - error.missingArgMsg + MissingImplicitArgument(arg, pt, where, paramSymWithMethodCallTree, ignoredInstanceNormalImport) } /** A string indicating the formal parameter corresponding to a missing argument */ @@ -939,10 +933,10 @@ trait Implicits: val qt = qual.tpe.widen val qt1 = qt.dealiasKeepAnnots def addendum = if (qt1 eq qt) "" else (i"\nThe required type is an alias of: $qt1") - em"parameter of ${qual.tpe.widen}$addendum" + i"parameter of ${qual.tpe.widen}$addendum" case _ => - em"${ if paramName.is(EvidenceParamName) then "an implicit parameter" - else s"parameter $paramName" } of $methodStr" + i"${ if paramName.is(EvidenceParamName) then "an implicit parameter" + else s"parameter $paramName" } of $methodStr" } /** A CanEqual[T, U] instance is assumed @@ -1050,7 +1044,8 @@ trait Implicits: withMode(Mode.OldOverloadingResolution)(inferImplicit(pt, argument, span)) match { case altResult: SearchSuccess => report.migrationWarning( - s"According to new implicit resolution rules, this will be ambiguous:\n${result.reason.explanation}", + result.reason.msg + .prepend(s"According to new implicit resolution rules, this will be ambiguous:\n"), ctx.source.atSpan(span)) altResult case _ => @@ -1357,13 +1352,13 @@ trait Implicits: def warnAmbiguousNegation(ambi: AmbiguousImplicits) = report.migrationWarning( - i"""Ambiguous implicits ${ambi.alt1.ref.symbol.showLocated} and ${ambi.alt2.ref.symbol.showLocated} - |seem to be used to implement a local failure in order to negate an implicit search. - |According to the new implicit resolution rules this is no longer possible; - |the search will fail with a global ambiguity error instead. - | - |Consider using the scala.util.NotGiven class to implement similar functionality.""", - srcPos) + em"""Ambiguous implicits ${ambi.alt1.ref.symbol.showLocated} and ${ambi.alt2.ref.symbol.showLocated} + |seem to be used to implement a local failure in order to negate an implicit search. + |According to the new implicit resolution rules this is no longer possible; + |the search will fail with a global ambiguity error instead. + | + |Consider using the scala.util.NotGiven class to implement similar functionality.""", + srcPos) /** Compare the length of the baseClasses of two symbols (except for objects, * where we use the length of the companion class instead if it's bigger). diff --git a/tests/pos-with-compiler-cc/dotc/typer/Inferencing.scala b/tests/pos-with-compiler-cc/dotc/typer/Inferencing.scala index 27b83e025cf9..9d2db773c4d4 100644 --- a/tests/pos-with-compiler-cc/dotc/typer/Inferencing.scala +++ b/tests/pos-with-compiler-cc/dotc/typer/Inferencing.scala @@ -6,15 +6,14 @@ import core._ import ast._ import Contexts._, Types._, Flags._, Symbols._ import ProtoTypes._ -import NameKinds.{AvoidNameKind, UniqueName} +import NameKinds.UniqueName import util.Spans._ -import util.{Stats, SimpleIdentityMap, SrcPos} +import util.{Stats, SimpleIdentityMap, SimpleIdentitySet, SrcPos} import Decorators._ import config.Printers.{gadts, typr} import annotation.tailrec import reporting._ import collection.mutable - import scala.annotation.internal.sharable object Inferencing { @@ -574,7 +573,7 @@ trait Inferencing { this: Typer => * Then `Y` also occurs co-variantly in `T` because it needs to be minimized in order to constrain * `T` the least. See `variances` for more detail. */ - def interpolateTypeVars(tree: Tree, pt: Type, locked: TypeVars)(using Context): tree.type = { + def interpolateTypeVars(tree: Tree, pt: Type, locked: TypeVars)(using Context): tree.type = val state = ctx.typerState // Note that some variables in `locked` might not be in `state.ownedVars` @@ -583,7 +582,7 @@ trait Inferencing { this: Typer => // `qualifying`. val ownedVars = state.ownedVars - if ((ownedVars ne locked) && !ownedVars.isEmpty) { + if (ownedVars ne locked) && !ownedVars.isEmpty then val qualifying = ownedVars -- locked if (!qualifying.isEmpty) { typr.println(i"interpolate $tree: ${tree.tpe.widen} in $state, pt = $pt, owned vars = ${state.ownedVars.toList}%, %, qualifying = ${qualifying.toList}%, %, previous = ${locked.toList}%, % / ${state.constraint}") @@ -619,44 +618,67 @@ trait Inferencing { this: Typer => if state.reporter.hasUnreportedErrors then return tree def constraint = state.constraint - type InstantiateQueue = mutable.ListBuffer[(TypeVar, Boolean)] - val toInstantiate = new InstantiateQueue - for tvar <- qualifying do - if !tvar.isInstantiated && constraint.contains(tvar) && tvar.nestingLevel >= ctx.nestingLevel then - constrainIfDependentParamRef(tvar, tree) - // Needs to be checked again, since previous interpolations could already have - // instantiated `tvar` through unification. - val v = vs(tvar) - if v == null then - // Even though `tvar` is non-occurring in `v`, the specific - // instantiation we pick still matters because `tvar` might appear - // in the bounds of a non-`qualifying` type variable in the - // constraint. - // In particular, if `tvar` was created as the upper or lower - // bound of an existing variable by `LevelAvoidMap`, we - // instantiate it in the direction corresponding to the - // original variable which might be further constrained later. - // Otherwise, we simply rely on `hasLowerBound`. - val name = tvar.origin.paramName - val fromBelow = - name.is(AvoidNameKind.UpperBound) || - !name.is(AvoidNameKind.LowerBound) && tvar.hasLowerBound - typr.println(i"interpolate non-occurring $tvar in $state in $tree: $tp, fromBelow = $fromBelow, $constraint") - toInstantiate += ((tvar, fromBelow)) - else if v.intValue != 0 then - typr.println(i"interpolate $tvar in $state in $tree: $tp, fromBelow = ${v.intValue == 1}, $constraint") - toInstantiate += ((tvar, v.intValue == 1)) - else comparing(cmp => - if !cmp.levelOK(tvar.nestingLevel, ctx.nestingLevel) then - // Invariant: The type of a tree whose enclosing scope is level - // N only contains type variables of level <= N. - typr.println(i"instantiate nonvariant $tvar of level ${tvar.nestingLevel} to a type variable of level <= ${ctx.nestingLevel}, $constraint") - cmp.atLevel(ctx.nestingLevel, tvar.origin) - else - typr.println(i"no interpolation for nonvariant $tvar in $state") - ) - /** Instantiate all type variables in `buf` in the indicated directions. + /** Values of this type report type variables to instantiate with variance indication: + * +1 variable appears covariantly, can be instantiated from lower bound + * -1 variable appears contravariantly, can be instantiated from upper bound + * 0 variable does not appear at all, can be instantiated from either bound + */ + type ToInstantiate = List[(TypeVar, Int)] + + val toInstantiate: ToInstantiate = + val buf = new mutable.ListBuffer[(TypeVar, Int)] + for tvar <- qualifying do + if !tvar.isInstantiated && constraint.contains(tvar) && tvar.nestingLevel >= ctx.nestingLevel then + constrainIfDependentParamRef(tvar, tree) + if !tvar.isInstantiated then + // isInstantiated needs to be checked again, since previous interpolations could already have + // instantiated `tvar` through unification. + val v = vs(tvar) + if v == null then buf += ((tvar, 0)) + else if v.intValue != 0 then buf += ((tvar, v.intValue)) + else comparing(cmp => + if !cmp.levelOK(tvar.nestingLevel, ctx.nestingLevel) then + // Invariant: The type of a tree whose enclosing scope is level + // N only contains type variables of level <= N. + typr.println(i"instantiate nonvariant $tvar of level ${tvar.nestingLevel} to a type variable of level <= ${ctx.nestingLevel}, $constraint") + cmp.atLevel(ctx.nestingLevel, tvar.origin) + else + typr.println(i"no interpolation for nonvariant $tvar in $state") + ) + buf.toList + + def typeVarsIn(xs: ToInstantiate): TypeVars = + xs.foldLeft(SimpleIdentitySet.empty: TypeVars)((tvs, tvi) => tvs + tvi._1) + + /** Filter list of proposed instantiations so that they don't constrain further + * the current constraint. + */ + def filterByDeps(tvs0: ToInstantiate): ToInstantiate = + val excluded = // ignore dependencies from other variables that are being instantiated + typeVarsIn(tvs0) + def step(tvs: ToInstantiate): ToInstantiate = tvs match + case tvs @ (hd @ (tvar, v)) :: tvs1 => + def aboveOK = !constraint.dependsOn(tvar, excluded, co = true) + def belowOK = !constraint.dependsOn(tvar, excluded, co = false) + if v == 0 && !aboveOK then + step((tvar, 1) :: tvs1) + else if v == 0 && !belowOK then + step((tvar, -1) :: tvs1) + else if v == -1 && !aboveOK || v == 1 && !belowOK then + typr.println(i"drop $tvar, $v in $tp, $pt, qualifying = ${qualifying.toList}, tvs0 = ${tvs0.toList}%, %, excluded = ${excluded.toList}, $constraint") + step(tvs1) + else // no conflict, keep the instantiation proposal + tvs.derivedCons(hd, step(tvs1)) + case Nil => + Nil + val tvs1 = step(tvs0) + if tvs1 eq tvs0 then tvs1 + else filterByDeps(tvs1) // filter again with smaller excluded set + end filterByDeps + + /** Instantiate all type variables in `tvs` in the indicated directions, + * as described in the doc comment of `ToInstantiate`. * If a type variable A is instantiated from below, and there is another * type variable B in `buf` that is known to be smaller than A, wait and * instantiate all other type variables before trying to instantiate A again. @@ -685,29 +707,37 @@ trait Inferencing { this: Typer => * * V2 := V3, O2 := O3 */ - def doInstantiate(buf: InstantiateQueue): Unit = - if buf.nonEmpty then - val suspended = new InstantiateQueue - while buf.nonEmpty do - val first @ (tvar, fromBelow) = buf.head - buf.dropInPlace(1) - if !tvar.isInstantiated then - val suspend = buf.exists{ (following, _) => - if fromBelow then - constraint.isLess(following.origin, tvar.origin) - else - constraint.isLess(tvar.origin, following.origin) + def doInstantiate(tvs: ToInstantiate): Unit = + + /** Try to instantiate `tvs`, return any suspended type variables */ + def tryInstantiate(tvs: ToInstantiate): ToInstantiate = tvs match + case (hd @ (tvar, v)) :: tvs1 => + val fromBelow = v == 1 || (v == 0 && tvar.hasLowerBound) + typr.println( + i"interpolate${if v == 0 then " non-occurring" else ""} $tvar in $state in $tree: $tp, fromBelow = $fromBelow, $constraint") + if tvar.isInstantiated then + tryInstantiate(tvs1) + else + val suspend = tvs1.exists{ (following, _) => + if fromBelow + then constraint.isLess(following.origin, tvar.origin) + else constraint.isLess(tvar.origin, following.origin) } - if suspend then suspended += first else tvar.instantiate(fromBelow) - end if - end while - doInstantiate(suspended) + if suspend then + typr.println(i"suspended: $hd") + hd :: tryInstantiate(tvs1) + else + tvar.instantiate(fromBelow) + tryInstantiate(tvs1) + case Nil => Nil + if tvs.nonEmpty then doInstantiate(tryInstantiate(tvs)) end doInstantiate - doInstantiate(toInstantiate) + + doInstantiate(filterByDeps(toInstantiate)) } - } + end if tree - } + end interpolateTypeVars /** If `tvar` represents a parameter of a dependent method type in the current `call` * approximate it from below with the type of the actual argument. Skolemize that diff --git a/tests/pos-with-compiler-cc/dotc/typer/Namer.scala b/tests/pos-with-compiler-cc/dotc/typer/Namer.scala index 6aab561c44b7..0c5d43ccd5e9 100644 --- a/tests/pos-with-compiler-cc/dotc/typer/Namer.scala +++ b/tests/pos-with-compiler-cc/dotc/typer/Namer.scala @@ -201,7 +201,7 @@ class Namer { typer: Typer => case tree: MemberDef => SymDenotations.canBeLocal(tree.name, flags) case _ => false if !ok then - report.error(i"modifier(s) `${flags.flagsString}` incompatible with $kind definition", tree.srcPos) + report.error(em"modifier(s) `${flags.flagsString}` incompatible with $kind definition", tree.srcPos) if adapted.is(Private) && canBeLocal then adapted | Local else adapted } @@ -462,7 +462,7 @@ class Namer { typer: Typer => if isProvisional then typr.println(i"provisional superclass $first for $cls") first = AnnotatedType(first, Annotation(defn.ProvisionalSuperClassAnnot)) - checkFeasibleParent(first, cls.srcPos, em" in inferred superclass $first") :: parents + checkFeasibleParent(first, cls.srcPos, i" in inferred superclass $first") :: parents end ensureFirstIsClass /** Add child annotation for `child` to annotations of `cls`. The annotation @@ -762,7 +762,7 @@ class Namer { typer: Typer => } def missingType(sym: Symbol, modifier: String)(using Context): Unit = { - report.error(s"${modifier}type of implicit definition needs to be given explicitly", sym.srcPos) + report.error(em"${modifier}type of implicit definition needs to be given explicitly", sym.srcPos) sym.resetFlag(GivenOrImplicit) } @@ -831,7 +831,7 @@ class Namer { typer: Typer => for (annotTree <- original.mods.annotations) { val cls = typedAheadAnnotationClass(annotTree)(using annotCtx) if (cls eq sym) - report.error("An annotation class cannot be annotated with iself", annotTree.srcPos) + report.error(em"An annotation class cannot be annotated with iself", annotTree.srcPos) else { val ann = Annotation.deferred(cls)(typedAheadExpr(annotTree)(using annotCtx)) sym.addAnnotation(ann) @@ -1249,7 +1249,7 @@ class Namer { typer: Typer => val reason = mbrs.map(canForward(_, alias)).collect { case CanForward.No(whyNot) => i"\n$path.$name cannot be exported because it $whyNot" }.headOption.getOrElse("") - report.error(i"""no eligible member $name at $path$reason""", ctx.source.atSpan(span)) + report.error(em"""no eligible member $name at $path$reason""", ctx.source.atSpan(span)) else targets += alias @@ -1314,7 +1314,7 @@ class Namer { typer: Typer => case _ => 0 if cmp == 0 then report.error( - ex"""Clashing exports: The exported + em"""Clashing exports: The exported | ${forwarder.rhs.symbol}: ${alt1.widen} |and ${forwarder1.rhs.symbol}: ${alt2.widen} |have the same signature after erasure and overloading resolution could not disambiguate.""", @@ -1437,7 +1437,7 @@ class Namer { typer: Typer => case mt: MethodType if cls.is(Case) && mt.isParamDependent => // See issue #8073 for background report.error( - i"""Implementation restriction: case classes cannot have dependencies between parameters""", + em"""Implementation restriction: case classes cannot have dependencies between parameters""", cls.srcPos) case _ => @@ -1618,12 +1618,14 @@ class Namer { typer: Typer => def typedAheadExpr(tree: Tree, pt: Type = WildcardType)(using Context): tpd.Tree = typedAhead(tree, typer.typedExpr(_, pt)) - def typedAheadAnnotationClass(tree: Tree)(using Context): Symbol = tree match { + def typedAheadAnnotationClass(tree: Tree)(using Context): Symbol = tree match case Apply(fn, _) => typedAheadAnnotationClass(fn) case TypeApply(fn, _) => typedAheadAnnotationClass(fn) case Select(qual, nme.CONSTRUCTOR) => typedAheadAnnotationClass(qual) case New(tpt) => typedAheadType(tpt).tpe.classSymbol - } + case TypedSplice(_) => + val sym = tree.symbol + if sym.isConstructor then sym.owner else sym /** Enter and typecheck parameter list */ def completeParams(params: List[MemberDef])(using Context): Unit = { @@ -1687,8 +1689,10 @@ class Namer { typer: Typer => if !Config.checkLevelsOnConstraints then val hygienicType = TypeOps.avoid(rhsType, termParamss.flatten) if (!hygienicType.isValueType || !(hygienicType <:< tpt.tpe)) - report.error(i"return type ${tpt.tpe} of lambda cannot be made hygienic;\n" + - i"it is not a supertype of the hygienic type $hygienicType", mdef.srcPos) + report.error( + em"""return type ${tpt.tpe} of lambda cannot be made hygienic + |it is not a supertype of the hygienic type $hygienicType""", + mdef.srcPos) //println(i"lifting $rhsType over $termParamss -> $hygienicType = ${tpt.tpe}") //println(TypeComparer.explained { implicit ctx => hygienicType <:< tpt.tpe }) case _ => diff --git a/tests/pos-with-compiler-cc/dotc/typer/QuotesAndSplices.scala b/tests/pos-with-compiler-cc/dotc/typer/QuotesAndSplices.scala index fa29f450be2a..2fe5770c5b4b 100644 --- a/tests/pos-with-compiler-cc/dotc/typer/QuotesAndSplices.scala +++ b/tests/pos-with-compiler-cc/dotc/typer/QuotesAndSplices.scala @@ -54,7 +54,7 @@ trait QuotesAndSplices { val msg = em"""Quoted types `'[..]` can only be used in patterns. | |Hint: To get a scala.quoted.Type[T] use scala.quoted.Type.of[T] instead. - |""".stripMargin + |""" report.error(msg, tree.srcPos) EmptyTree else @@ -87,7 +87,7 @@ trait QuotesAndSplices { ref(defn.QuotedRuntime_exprSplice).appliedToType(argType).appliedTo(pat) } else { - report.error(i"Type must be fully defined.\nConsider annotating the splice using a type ascription:\n ($tree: XYZ).", tree.expr.srcPos) + report.error(em"Type must be fully defined.\nConsider annotating the splice using a type ascription:\n ($tree: XYZ).", tree.expr.srcPos) tree.withType(UnspecifiedErrorType) } else { @@ -123,7 +123,7 @@ trait QuotesAndSplices { assert(ctx.mode.is(Mode.QuotedPattern)) val untpd.Apply(splice: untpd.Splice, args) = tree: @unchecked if !isFullyDefined(pt, ForceDegree.flipBottom) then - report.error(i"Type must be fully defined.", splice.srcPos) + report.error(em"Type must be fully defined.", splice.srcPos) tree.withType(UnspecifiedErrorType) else if splice.isInBraces then // ${x}(...) match an application val typedArgs = args.map(arg => typedExpr(arg)) @@ -172,10 +172,10 @@ trait QuotesAndSplices { report.error("Splice ${...} outside quotes '{...} or inline method", tree.srcPos) else if (level < 0) report.error( - s"""Splice $${...} at level $level. - | - |Inline method may contain a splice at level 0 but the contents of this splice cannot have a splice. - |""".stripMargin, tree.srcPos + em"""Splice $${...} at level $level. + | + |Inline method may contain a splice at level 0 but the contents of this splice cannot have a splice. + |""", tree.srcPos ) /** Split a typed quoted pattern is split into its type bindings, pattern expression and inner patterns. @@ -263,7 +263,7 @@ trait QuotesAndSplices { transformTypeBindingTypeDef(PatMatGivenVarName.fresh(tdef.name.toTermName), tdef, typePatBuf) else if tdef.symbol.isClass then val kind = if tdef.symbol.is(Module) then "objects" else "classes" - report.error("Implementation restriction: cannot match " + kind, tree.srcPos) + report.error(em"Implementation restriction: cannot match $kind", tree.srcPos) EmptyTree else super.transform(tree) diff --git a/tests/pos-with-compiler-cc/dotc/typer/RefChecks.scala b/tests/pos-with-compiler-cc/dotc/typer/RefChecks.scala index 4d2c7ffdfe7d..e79f22197cca 100644 --- a/tests/pos-with-compiler-cc/dotc/typer/RefChecks.scala +++ b/tests/pos-with-compiler-cc/dotc/typer/RefChecks.scala @@ -59,11 +59,9 @@ object RefChecks { // constructors of different classes are allowed to have defaults if (haveDefaults.exists(x => !x.isConstructor) || owners.distinct.size < haveDefaults.size) report.error( - "in " + clazz + - ", multiple overloaded alternatives of " + haveDefaults.head + - " define default arguments" + ( - if (owners.forall(_ == clazz)) "." - else ".\nThe members with defaults are defined in " + owners.map(_.showLocated).mkString("", " and ", ".")), + em"in $clazz, multiple overloaded alternatives of ${haveDefaults.head} define default arguments${ + if owners.forall(_ == clazz) then "." + else i".\nThe members with defaults are defined in ${owners.map(_.showLocated).mkString("", " and ", ".")}"}", clazz.srcPos) } } @@ -320,20 +318,10 @@ object RefChecks { report.error(msg.append(othersMsg), clazz.srcPos) } - def infoString(sym: Symbol) = infoString0(sym, sym.owner != clazz) - def infoStringWithLocation(sym: Symbol) = infoString0(sym, true) - - def infoString0(sym: Symbol, showLocation: Boolean) = { - val sym1 = sym.underlyingSymbol - def info = self.memberInfo(sym1) - val infoStr = - if (sym1.isAliasType) i", which equals ${info.bounds.hi}" - else if (sym1.isAbstractOrParamType && info != TypeBounds.empty) i" with bounds$info" - else if (sym1.is(Module)) "" - else if (sym1.isTerm) i" of type $info" - else "" - i"${if (showLocation) sym1.showLocated else sym1}$infoStr" - } + def infoString(sym: Symbol) = + err.infoString(sym, self, showLocation = sym.owner != clazz) + def infoStringWithLocation(sym: Symbol) = + err.infoString(sym, self, showLocation = true) /* Check that all conditions for overriding `other` by `member` * of class `clazz` are met. @@ -348,20 +336,9 @@ object RefChecks { def noErrorType = !memberTp(self).isErroneous && !otherTp(self).isErroneous - def overrideErrorMsg(msg: String, compareTypes: Boolean = false): Message = { - val isConcreteOverAbstract = - (other.owner isSubClass member.owner) && other.is(Deferred) && !member.is(Deferred) - val addendum = - if isConcreteOverAbstract then - ";\n (Note that %s is abstract,\n and is therefore overridden by concrete %s)".format( - infoStringWithLocation(other), - infoStringWithLocation(member)) - else "" - val fullMsg = - s"error overriding ${infoStringWithLocation(other)};\n ${infoString(member)} $msg$addendum" - if compareTypes then OverrideTypeMismatchError(fullMsg, memberTp(self), otherTp(self)) - else OverrideError(fullMsg) - } + def overrideErrorMsg(core: Context ?-> String, compareTypes: Boolean = false): Message = + val (mtp, otp) = if compareTypes then (memberTp(self), otherTp(self)) else (NoType, NoType) + OverrideError(core, self, member, other, mtp, otp) def compatTypes(memberTp: Type, otherTp: Type): Boolean = try @@ -397,7 +374,7 @@ object RefChecks { def overrideDeprecation(what: String, member: Symbol, other: Symbol, fix: String): Unit = report.deprecationWarning( - s"overriding $what${infoStringWithLocation(other)} is deprecated;\n ${infoString(member)} should be $fix.", + em"overriding $what${infoStringWithLocation(other)} is deprecated;\n ${infoString(member)} should be $fix.", if member.owner == clazz then member.srcPos else clazz.srcPos) def autoOverride(sym: Symbol) = @@ -483,7 +460,7 @@ object RefChecks { if (autoOverride(member) || other.owner.isAllOf(JavaInterface) && warnOnMigration( - "`override` modifier required when a Java 8 default method is re-implemented".toMessage, + em"`override` modifier required when a Java 8 default method is re-implemented", member.srcPos, version = `3.0`)) member.setFlag(Override) else if (member.isType && self.memberInfo(member) =:= self.memberInfo(other)) @@ -540,7 +517,7 @@ object RefChecks { overrideError(i"cannot override val parameter ${other.showLocated}") else report.deprecationWarning( - i"overriding val parameter ${other.showLocated} is deprecated, will be illegal in a future version", + em"overriding val parameter ${other.showLocated} is deprecated, will be illegal in a future version", member.srcPos) else if !other.isExperimental && member.hasAnnotation(defn.ExperimentalAnnot) then // (1.12) overrideError("may not override non-experimental member") @@ -563,7 +540,7 @@ object RefChecks { val abstractErrors = new mutable.ListBuffer[String] def abstractErrorMessage = // a little formatting polish - if (abstractErrors.size <= 2) abstractErrors mkString " " + if (abstractErrors.size <= 2) abstractErrors.mkString(" ") else abstractErrors.tail.mkString(abstractErrors.head + ":\n", "\n", "") def abstractClassError(mustBeMixin: Boolean, msg: String): Unit = { @@ -818,7 +795,7 @@ object RefChecks { for (baseCls <- caseCls.info.baseClasses.tail) if (baseCls.typeParams.exists(_.paramVarianceSign != 0)) for (problem <- variantInheritanceProblems(baseCls, caseCls, "non-variant", "case ")) - report.errorOrMigrationWarning(problem(), clazz.srcPos, from = `3.0`) + report.errorOrMigrationWarning(problem, clazz.srcPos, from = `3.0`) checkNoAbstractMembers() if (abstractErrors.isEmpty) checkNoAbstractDecls(clazz) @@ -849,7 +826,7 @@ object RefChecks { if cls.paramAccessors.nonEmpty && !mixins.contains(cls) problem <- variantInheritanceProblems(cls, clazz.asClass.superClass, "parameterized", "super") } - report.error(problem(), clazz.srcPos) + report.error(problem, clazz.srcPos) } checkParameterizedTraitsOK() @@ -863,13 +840,13 @@ object RefChecks { * Return an optional by name error message if this test fails. */ def variantInheritanceProblems( - baseCls: Symbol, middle: Symbol, baseStr: String, middleStr: String): Option[() -> String] = { + baseCls: Symbol, middle: Symbol, baseStr: String, middleStr: String): Option[Message] = { val superBT = self.baseType(middle) val thisBT = self.baseType(baseCls) val combinedBT = superBT.baseType(baseCls) if (combinedBT =:= thisBT) None // ok else - Some(() => + Some( em"""illegal inheritance: $clazz inherits conflicting instances of $baseStr base $baseCls. | | Direct basetype: $thisBT @@ -966,7 +943,7 @@ object RefChecks { for bc <- cls.baseClasses.tail do val other = sym.matchingDecl(bc, cls.thisType) if other.exists then - report.error(i"private $sym cannot override ${other.showLocated}", sym.srcPos) + report.error(em"private $sym cannot override ${other.showLocated}", sym.srcPos) end checkNoPrivateOverrides /** Check that unary method definition do not receive parameters. diff --git a/tests/pos-with-compiler-cc/dotc/typer/Synthesizer.scala b/tests/pos-with-compiler-cc/dotc/typer/Synthesizer.scala index b96d80345bc3..7bc9619922db 100644 --- a/tests/pos-with-compiler-cc/dotc/typer/Synthesizer.scala +++ b/tests/pos-with-compiler-cc/dotc/typer/Synthesizer.scala @@ -476,9 +476,9 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): if acceptableMsg.isEmpty && clsIsGenericSum then val elemLabels = cls.children.map(c => ConstantType(Constant(c.name.toString))) - def internalError(msg: -> String)(using Context): Unit = - report.error(i"""Internal error when synthesizing sum mirror for $cls: - |$msg""".stripMargin, ctx.source.atSpan(span)) + def internalError(msg: => String)(using Context): Unit = + report.error(em"""Internal error when synthesizing sum mirror for $cls: + |$msg""", ctx.source.atSpan(span)) def childPrefix(child: Symbol)(using Context): Type = val symPre = TypeOps.childPrefix(pre, cls, child) @@ -692,10 +692,11 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): val manifest = synthesize(fullyDefinedType(arg, "Manifest argument", ctx.source.atSpan(span)), kind, topLevel = true) if manifest != EmptyTree then report.deprecationWarning( - i"""Compiler synthesis of Manifest and OptManifest is deprecated, instead - |replace with the type `scala.reflect.ClassTag[$arg]`. - |Alternatively, consider using the new metaprogramming features of Scala 3, - |see https://docs.scala-lang.org/scala3/reference/metaprogramming.html""", ctx.source.atSpan(span)) + em"""Compiler synthesis of Manifest and OptManifest is deprecated, instead + |replace with the type `scala.reflect.ClassTag[$arg]`. + |Alternatively, consider using the new metaprogramming features of Scala 3, + |see https://docs.scala-lang.org/scala3/reference/metaprogramming.html""", + ctx.source.atSpan(span)) withNoErrors(manifest) case _ => EmptyTreeNoError diff --git a/tests/pos-with-compiler-cc/dotc/typer/TypeAssigner.scala b/tests/pos-with-compiler-cc/dotc/typer/TypeAssigner.scala index b90409e72364..68bdd4f4970a 100644 --- a/tests/pos-with-compiler-cc/dotc/typer/TypeAssigner.scala +++ b/tests/pos-with-compiler-cc/dotc/typer/TypeAssigner.scala @@ -31,8 +31,9 @@ trait TypeAssigner { c case _ => report.error( - if (qual.isEmpty) tree.show + " can be used only in a class, object, or template" - else qual.show + " is not an enclosing class", tree.srcPos) + if qual.isEmpty then em"$tree can be used only in a class, object, or template" + else em"$qual is not an enclosing class", + tree.srcPos) NoSymbol } } @@ -127,7 +128,7 @@ trait TypeAssigner { def arrayElemType = qual1.tpe.widen match case JavaArrayType(elemtp) => elemtp case qualType => - report.error("Expected Array but was " + qualType.show, tree.srcPos) + report.error(em"Expected Array but was $qualType", tree.srcPos) defn.NothingType val name = tree.name @@ -167,26 +168,13 @@ trait TypeAssigner { case _ => false def addendum = err.selectErrorAddendum(tree, qual, qualType, importSuggestionAddendum, foundWithoutNull) val msg: Message = - if tree.name == nme.CONSTRUCTOR then ex"$qualType does not have a constructor".toMessage + if tree.name == nme.CONSTRUCTOR then em"$qualType does not have a constructor" else NotAMember(qualType, tree.name, kind, addendum) errorType(msg, tree.srcPos) def inaccessibleErrorType(tpe: NamedType, superAccess: Boolean, pos: SrcPos)(using Context): Type = - val pre = tpe.prefix - val name = tpe.name - val alts = tpe.denot.alternatives.map(_.symbol).filter(_.exists) - val whatCanNot = alts match - case Nil => - em"$name cannot" - case sym :: Nil => - em"${if (sym.owner == pre.typeSymbol) sym.show else sym.showLocated} cannot" - case _ => - em"none of the overloaded alternatives named $name can" - val where = if (ctx.owner.exists) s" from ${ctx.owner.enclosingClass}" else "" - val whyNot = new StringBuffer - alts.foreach(_.isAccessibleFrom(pre, superAccess, whyNot)) if tpe.isError then tpe - else errorType(ex"$whatCanNot be accessed as a member of $pre$where.$whyNot", pos) + else errorType(CannotBeAccessed(tpe, superAccess), pos) def processAppliedType(tree: untpd.Tree, tp: Type)(using Context): Type = tp match case AppliedType(tycon, args) => diff --git a/tests/pos-with-compiler-cc/dotc/typer/Typer.scala b/tests/pos-with-compiler-cc/dotc/typer/Typer.scala index 5d708281e6fa..706750345801 100644 --- a/tests/pos-with-compiler-cc/dotc/typer/Typer.scala +++ b/tests/pos-with-compiler-cc/dotc/typer/Typer.scala @@ -284,7 +284,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def checkUnambiguous(found: Type) = val other = recur(selectors.tail) if other.exists && found.exists && found != other then - fail(em"reference to `$name` is ambiguous; it is imported twice".toMessage) + fail(em"reference to `$name` is ambiguous; it is imported twice") found if selector.rename == termName && selector.rename != nme.WILDCARD then @@ -528,7 +528,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val found = findRef(name, pt, EmptyFlags, EmptyFlags, tree.srcPos) if foundUnderScala2.exists && !(foundUnderScala2 =:= found) then report.migrationWarning( - ex"""Name resolution will change. + em"""Name resolution will change. | currently selected : $foundUnderScala2 | in the future, without -source 3.0-migration: $found""", tree.srcPos) foundUnderScala2 @@ -587,7 +587,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer else if ctx.owner.isConstructor && !ctx.owner.isPrimaryConstructor && ctx.owner.owner.unforcedDecls.lookup(tree.name).exists then // we are in the arguments of a this(...) constructor call - errorTree(tree, ex"$tree is not accessible from constructor arguments") + errorTree(tree, em"$tree is not accessible from constructor arguments") else errorTree(tree, MissingIdent(tree, kind, name)) end typedIdent @@ -1213,8 +1213,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer && defn.isContextFunctionType(pt1.nonPrivateMember(nme.apply).info.finalResultType) then report.error( - i"""Implementation restriction: Expected result type $pt1 - |is a curried dependent context function type. Such types are not yet supported.""", + em"""Implementation restriction: Expected result type $pt1 + |is a curried dependent context function type. Such types are not yet supported.""", pos) pt1 match { case tp: TypeParamRef => @@ -1325,7 +1325,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val appDef = typed(appDef0).asInstanceOf[DefDef] val mt = appDef.symbol.info.asInstanceOf[MethodType] if (mt.isParamDependent) - report.error(i"$mt is an illegal function type because it has inter-parameter dependencies", tree.srcPos) + report.error(em"$mt is an illegal function type because it has inter-parameter dependencies", tree.srcPos) val resTpt = TypeTree(mt.nonDependentResultApprox).withSpan(body.span) val typeArgs = appDef.termParamss.head.map(_.tpt) :+ resTpt val tycon = TypeTree(funSym.typeRef) @@ -1529,7 +1529,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer // Replace the underspecified expected type by one based on the closure method type defn.PartialFunctionOf(mt.firstParamTypes.head, mt.resultType) else - report.error(ex"result type of lambda is an underspecified SAM type $pt", tree.srcPos) + report.error(em"result type of lambda is an underspecified SAM type $pt", tree.srcPos) pt TypeTree(targetTpe) case _ => @@ -2099,6 +2099,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer && checkedArgs(1).tpe.derivesFrom(defn.RuntimeExceptionClass) then report.error(em"throws clause cannot be defined for RuntimeException", checkedArgs(1).srcPos) + else if tycon == defn.IntoType then + // is defined in package scala but this should be hidden from user programs + report.error(em"not found: ", tpt1.srcPos) else if (ctx.isJava) if tycon eq defn.ArrayClass then checkedArgs match { @@ -2447,7 +2450,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer // error if the same parent was explicitly added in user code. if !tree.span.isSourceDerived then return EmptyTree - if !ctx.isAfterTyper then report.error(i"$psym is extended twice", tree.srcPos) + if !ctx.isAfterTyper then report.error(em"$psym is extended twice", tree.srcPos) else seenParents += psym val result = ensureConstrCall(cls, parent, psym)(using superCtx) @@ -2564,7 +2567,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer && effectiveOwner.is(Trait) && !effectiveOwner.derivesFrom(defn.ObjectClass) then - report.error(i"$cls cannot be defined in universal $effectiveOwner", cdef.srcPos) + report.error(em"$cls cannot be defined in universal $effectiveOwner", cdef.srcPos) // Temporarily set the typed class def as root tree so that we have at least some // information in the IDE in case we never reach `SetRootTree`. @@ -2671,7 +2674,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer // Package will not exist if a duplicate type has already been entered, see `tests/neg/1708.scala` errorTree(tree, if pkg.exists then PackageNameAlreadyDefined(pkg) - else i"package ${tree.pid.name} does not exist".toMessage) + else em"package ${tree.pid.name} does not exist") end typedPackageDef def typedAnnotated(tree: untpd.Annotated, pt: Type)(using Context): Tree = { @@ -2751,8 +2754,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer if ((prefix ++ suffix).isEmpty) "simply leave out the trailing ` _`" else s"use `$prefix$suffix` instead" report.errorOrMigrationWarning( - i"""The syntax ` _` is no longer supported; - |you can $remedy""", + em"""The syntax ` _` is no longer supported; + |you can $remedy""", tree.srcPos, from = future) if sourceVersion.isMigrating then @@ -2824,7 +2827,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val tupleXXLobj = untpd.ref(defn.TupleXXLModule.termRef) val app = untpd.cpy.Apply(tree)(tupleXXLobj, elems.map(untpd.TypedSplice(_))) .withSpan(tree.span) - val app1 = typed(app, defn.TupleXXLClass.typeRef) + val app1 = typed(app, if ctx.mode.is(Mode.Pattern) then pt else defn.TupleXXLClass.typeRef) if (ctx.mode.is(Mode.Pattern)) app1 else { val elemTpes = elems.lazyZip(pts).map((elem, pt) => @@ -2884,7 +2887,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer else "" val namePos = tree.sourcePos.withSpan(tree.nameSpan) report.errorOrMigrationWarning( - s"`?` is not a valid type name$addendum", namePos, from = `3.0`) + em"`?` is not a valid type name$addendum", namePos, from = `3.0`) if tree.isClassDef then typedClassDef(tree, sym.asClass)(using ctx.localContext(tree, sym)) else @@ -2936,7 +2939,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case tree: untpd.TypedSplice => typedTypedSplice(tree) case tree: untpd.UnApply => typedUnApply(tree, pt) case tree: untpd.Tuple => typedTuple(tree, pt) - case tree: untpd.DependentTypeTree => completeTypeTree(untpd.TypeTree(), pt, tree) + case tree: untpd.DependentTypeTree => completeTypeTree(untpd.InferredTypeTree(), pt, tree) case tree: untpd.InfixOp => typedInfixOp(tree, pt) case tree: untpd.ParsedTry => typedTry(tree, pt) case tree @ untpd.PostfixOp(qual, Ident(nme.WILDCARD)) => typedAsFunction(tree, pt) @@ -3353,7 +3356,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case SearchSuccess(found, _, _, isExtension) => if isExtension then return found else - checkImplicitConversionUseOK(found) + checkImplicitConversionUseOK(found, selProto) return withoutMode(Mode.ImplicitsEnabled)(typedSelect(tree, pt, found)) case failure: SearchFailure => if failure.isAmbiguous then @@ -3737,7 +3740,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer if (!defn.isFunctionType(pt)) pt match { case SAMType(_) if !pt.classSymbol.hasAnnotation(defn.FunctionalInterfaceAnnot) => - report.warning(ex"${tree.symbol} is eta-expanded even though $pt does not have the @FunctionalInterface annotation.", tree.srcPos) + report.warning(em"${tree.symbol} is eta-expanded even though $pt does not have the @FunctionalInterface annotation.", tree.srcPos) case _ => } simplify(typed(etaExpand(tree, wtp, arity), pt), pt, locked) @@ -3797,8 +3800,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer } else { report.error( - """Scala 2 macro cannot be used in Dotty. See https://docs.scala-lang.org/scala3/reference/dropped-features/macros.html - |To turn this error into a warning, pass -Xignore-scala2-macros to the compiler""".stripMargin, tree.srcPos.startPos) + em"""Scala 2 macro cannot be used in Dotty. See https://docs.scala-lang.org/scala3/reference/dropped-features/macros.html + |To turn this error into a warning, pass -Xignore-scala2-macros to the compiler""", + tree.srcPos.startPos) tree } else TypeComparer.testSubType(tree.tpe.widenExpr, pt) match @@ -3997,7 +4001,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case SearchSuccess(found, _, _, isExtension) => if isExtension then found else - checkImplicitConversionUseOK(found) + checkImplicitConversionUseOK(found, pt) withoutMode(Mode.ImplicitsEnabled)(readapt(found)) case failure: SearchFailure => if (pt.isInstanceOf[ProtoType] && !failure.isAmbiguous) then @@ -4229,11 +4233,12 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case _ => if !config.Feature.scala2ExperimentalMacroEnabled then report.error( - """Scala 2 macro definition needs to be enabled - |by making the implicit value scala.language.experimental.macros visible. - |This can be achieved by adding the import clause 'import scala.language.experimental.macros' - |or by setting the compiler option -language:experimental.macros. - """.stripMargin, call.srcPos) + em"""Scala 2 macro definition needs to be enabled + |by making the implicit value scala.language.experimental.macros visible. + |This can be achieved by adding the import clause 'import scala.language.experimental.macros' + |or by setting the compiler option -language:experimental.macros. + """, + call.srcPos) call match case call: untpd.Ident => typedIdent(call, defn.AnyType) @@ -4248,7 +4253,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer typedTypeApply(call2, defn.AnyType) } case _ => - report.error("Invalid Scala 2 macro " + call.show, call.srcPos) + report.error(em"Invalid Scala 2 macro $call", call.srcPos) EmptyTree else typedExpr(call, defn.AnyType) diff --git a/tests/pos-with-compiler-cc/dotc/typer/VarianceChecker.scala b/tests/pos-with-compiler-cc/dotc/typer/VarianceChecker.scala index 53646558cf5c..bcfc9288d862 100644 --- a/tests/pos-with-compiler-cc/dotc/typer/VarianceChecker.scala +++ b/tests/pos-with-compiler-cc/dotc/typer/VarianceChecker.scala @@ -164,11 +164,11 @@ class VarianceChecker(using Context) { i"\n${hl("enum case")} ${towner.name} requires explicit declaration of $tvar to resolve this issue.\n$example" else "" - i"${varianceLabel(tvar.flags)} $tvar occurs in ${varianceLabel(required)} position in type ${sym.info} of $sym$enumAddendum" + em"${varianceLabel(tvar.flags)} $tvar occurs in ${varianceLabel(required)} position in type ${sym.info} of $sym$enumAddendum" if (migrateTo3 && (sym.owner.isConstructor || sym.ownersIterator.exists(_.isAllOf(ProtectedLocal)))) report.migrationWarning( - s"According to new variance rules, this is no longer accepted; need to annotate with @uncheckedVariance:\n$msg", + msg.prepend("According to new variance rules, this is no longer accepted; need to annotate with @uncheckedVariance\n"), pos) // patch(Span(pos.end), " @scala.annotation.unchecked.uncheckedVariance") // Patch is disabled until two TODOs are solved: diff --git a/tests/pos-with-compiler-cc/package.scala b/tests/pos-with-compiler-cc/package.scala index 1c6bb2310d80..1037670cae83 100644 --- a/tests/pos-with-compiler-cc/package.scala +++ b/tests/pos-with-compiler-cc/package.scala @@ -1,10 +1,6 @@ package dotty package object tools { - // Ensure this object is already classloaded, since it's only actually used - // when handling stack overflows and every operation (including class loading) - // risks failing. - dotty.tools.dotc.core.handleRecursive val ListOfNil: List[Nil.type] = Nil :: Nil @@ -50,4 +46,9 @@ package object tools { val e = if msg == null then AssertionError() else AssertionError("assertion failed: " + msg) e.setStackTrace(Array()) throw e -} + + // Ensure this object is already classloaded, since it's only actually used + // when handling stack overflows and every operation (including class loading) + // risks failing. + dotty.tools.dotc.core.handleRecursive + }