From dab9edb563ef82462128ad178dbd6fecc00198b2 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Tue, 11 Nov 2025 20:55:52 -0800 Subject: [PATCH 1/2] Inferred Contextual params are less nameable --- compiler/src/dotty/tools/dotc/ast/Desugar.scala | 8 +++++--- .../src/dotty/tools/dotc/core/NameKinds.scala | 2 ++ .../dotty/tools/dotc/printing/PlainPrinter.scala | 16 +++++++++++----- compiler/src/dotty/tools/dotc/typer/Typer.scala | 2 +- tests/neg-custom-args/captures/filevar.check | 6 ++++-- tests/neg-custom-args/captures/i15923.check | 10 ++++++---- tests/neg/i24375.scala | 12 ++++++++++++ 7 files changed, 41 insertions(+), 15 deletions(-) create mode 100644 tests/neg/i24375.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 8471b06b7e97..ab77350be26c 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -1987,10 +1987,12 @@ object desugar { .collect: case vd: ValDef => vd - def makeContextualFunction(formals: List[Tree], paramNamesOrNil: List[TermName], body: Tree, erasedParams: List[Boolean])(using Context): Function = + def makeContextualFunction(formals: List[Tree], paramNamesOrNil: List[TermName], body: Tree, erasedParams: List[Boolean], augmenting: Boolean = false)(using Context): Function = val paramNames = - if paramNamesOrNil.nonEmpty then paramNamesOrNil - else formals.map(_ => ContextFunctionParamName.fresh()) + if paramNamesOrNil.nonEmpty then + if augmenting then paramNamesOrNil.map(ContextFunctionParamName.fresh(_)) + else paramNamesOrNil + else List.fill(formals.length)(ContextFunctionParamName.fresh()) val params = for (tpt, pname) <- formals.zip(paramNames) yield ValDef(pname, tpt, EmptyTree).withFlags(Given | Param) FunctionWithMods(params, body, Modifiers(Given), erasedParams) diff --git a/compiler/src/dotty/tools/dotc/core/NameKinds.scala b/compiler/src/dotty/tools/dotc/core/NameKinds.scala index 6738c73113f9..d16bb3802b0c 100644 --- a/compiler/src/dotty/tools/dotc/core/NameKinds.scala +++ b/compiler/src/dotty/tools/dotc/core/NameKinds.scala @@ -303,10 +303,12 @@ object NameKinds { /** The name of an inferred contextual function parameter: * * val x: A ?=> B = b + * val f: (x: A) ?=> B = b * * becomes: * * val x: A ?=> B = (contextual$1: A) ?=> b + * val f: (x: A) ?=> B = (xcontextual$1: A) ?=> b */ val ContextFunctionParamName: UniqueNameKind = new UniqueNameKind("contextual$") diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index 90dfb72ef010..962133fbacc6 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -4,6 +4,7 @@ package printing import core.* import Texts.*, Types.*, Flags.*, Names.*, Symbols.*, NameOps.*, Constants.*, Denotations.* import StdNames.* +import NameKinds.ContextFunctionParamName import Contexts.* import Scopes.Scope, Denotations.Denotation, Annotations.Annotation import StdNames.nme @@ -361,9 +362,7 @@ class PlainPrinter(_ctx: Context) extends Printer { case tp: LazyRef => def refTxt = try toTextGlobal(tp.ref) - catch { - case ex: Throwable => Str("...") - } + catch case _: Throwable => Str("...") // reconsider catching errors "LazyRef(" ~ refTxt ~ ")" case Range(lo, hi) => toText(lo) ~ ".." ~ toText(hi) @@ -395,10 +394,17 @@ class PlainPrinter(_ctx: Context) extends Printer { Text(lam.paramRefs.map(paramText), ", ") } - protected def ParamRefNameString(name: Name): String = nameString(name) + protected def ParamRefNameString(name: Name): String = + nameString(name) protected def ParamRefNameString(param: ParamRef): String = - ParamRefNameString(param.binder.paramNames(param.paramNum)) + val name = param.binder.paramNames(param.paramNum) + ParamRefNameString: + if name.is(ContextFunctionParamName) then + name match + case ContextFunctionParamName(name, _) if !name.isEmpty => name + case name => name + else name /** The name of the symbol without a unique id. */ protected def simpleNameString(sym: Symbol): String = nameString(sym.name) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 164ff411e73b..42e192a09207 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -3912,7 +3912,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case _ => paramTypes.map(_ => false) } - val ifun = desugar.makeContextualFunction(paramTypes, paramNamesOrNil, tree, erasedParams) + val ifun = desugar.makeContextualFunction(paramTypes, paramNamesOrNil, tree, erasedParams, augmenting = true) typr.println(i"make contextual function $tree / $pt ---> $ifun") typedFunctionValue(ifun, pt) .tap: diff --git a/tests/neg-custom-args/captures/filevar.check b/tests/neg-custom-args/captures/filevar.check index ed3996b1e4d8..b706eceea7fd 100644 --- a/tests/neg-custom-args/captures/filevar.check +++ b/tests/neg-custom-args/captures/filevar.check @@ -5,10 +5,12 @@ |The leakage occurred when trying to match the following types: | |Found: (l: scala.caps.Capability^) ?->'s2 File^'s3 ->'s4 Unit - |Required: (l: scala.caps.Capability^) ?-> (f: File^{l}) => Unit + |Required: (l²: scala.caps.Capability^) ?-> (f: File^{l²}) => Unit | - |where: => refers to a root capability associated with the result type of (using l: scala.caps.Capability^): (f: File^{l}) => Unit + |where: => refers to a root capability associated with the result type of (using l²: scala.caps.Capability^): (f: File^{l²}) => Unit | ^ refers to the universal root capability + | l is a reference to a value parameter + | l² is a reference to a value parameter 16 | val o = Service() 17 | o.file = f 18 | o.log diff --git a/tests/neg-custom-args/captures/i15923.check b/tests/neg-custom-args/captures/i15923.check index 44c918ff57be..389903582bfe 100644 --- a/tests/neg-custom-args/captures/i15923.check +++ b/tests/neg-custom-args/captures/i15923.check @@ -5,10 +5,12 @@ |The leakage occurred when trying to match the following types: | |Found: (lcap: scala.caps.Capability^) ?->'s2 Cap^'s3 ->'s4 Id[Cap^'s5]^'s6 - |Required: (lcap: scala.caps.Capability^) ?-> Cap^{lcap} => Id[Cap^'s1]^'s7 + |Required: (lcap²: scala.caps.Capability^) ?-> Cap^{lcap²} => Id[Cap^'s1]^'s7 | - |where: => refers to a root capability associated with the result type of (using lcap: scala.caps.Capability^): Cap^{lcap} => Id[Cap^'s1]^'s7 - | ^ refers to the universal root capability - | cap is a root capability associated with the result type of (x$0: Cap^'s3): Id[Cap^'s5]^'s6 + |where: => refers to a root capability associated with the result type of (using lcap²: scala.caps.Capability^): Cap^{lcap²} => Id[Cap^'s1]^'s7 + | ^ refers to the universal root capability + | cap is a root capability associated with the result type of (x$0: Cap^'s3): Id[Cap^'s5]^'s6 + | lcap is a reference to a value parameter + | lcap² is a reference to a value parameter | | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i24375.scala b/tests/neg/i24375.scala new file mode 100644 index 000000000000..d6c3bf83cfbd --- /dev/null +++ b/tests/neg/i24375.scala @@ -0,0 +1,12 @@ +trait Ord[X]: + def compare(x: X, y: X): Int + type T + +trait Show[X]: + def show(x: X): String + +val f0: Show[String] ?=> String = summon[Show[String]].show("hello, world") * 2 + +val f1: (x: Show[String]) ?=> String = x.show("hello, world") * 2 // error + +val f2 = (x: Show[String]) ?=> x.show("hello, world") * 2 From e0a593967ba909258a8d19549d6d2ad53cebc39f Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Wed, 12 Nov 2025 03:00:01 -0800 Subject: [PATCH 2/2] Alt tweak --- compiler/src/dotty/tools/dotc/reporting/Message.scala | 6 +++--- tests/neg-custom-args/captures/filevar.check | 6 ++---- tests/neg-custom-args/captures/i15923.check | 10 ++++------ 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/Message.scala b/compiler/src/dotty/tools/dotc/reporting/Message.scala index f0bb57652fd9..e025594cad50 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Message.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Message.scala @@ -132,9 +132,9 @@ object Message: def sameSuperscript(cur: Recorded, existing: Recorded) = (cur eq existing) || (cur, existing).match - case (cur: ParamRef, existing: ParamRef) => - (cur.paramName eq existing.paramName) - && cur.binder.paramNames == existing.binder.paramNames + case (cur: ParamRef, existing: ParamRef) => true + //(cur.paramName eq existing.paramName) + //&& cur.binder.paramNames == existing.binder.paramNames case _ => false diff --git a/tests/neg-custom-args/captures/filevar.check b/tests/neg-custom-args/captures/filevar.check index b706eceea7fd..ed3996b1e4d8 100644 --- a/tests/neg-custom-args/captures/filevar.check +++ b/tests/neg-custom-args/captures/filevar.check @@ -5,12 +5,10 @@ |The leakage occurred when trying to match the following types: | |Found: (l: scala.caps.Capability^) ?->'s2 File^'s3 ->'s4 Unit - |Required: (l²: scala.caps.Capability^) ?-> (f: File^{l²}) => Unit + |Required: (l: scala.caps.Capability^) ?-> (f: File^{l}) => Unit | - |where: => refers to a root capability associated with the result type of (using l²: scala.caps.Capability^): (f: File^{l²}) => Unit + |where: => refers to a root capability associated with the result type of (using l: scala.caps.Capability^): (f: File^{l}) => Unit | ^ refers to the universal root capability - | l is a reference to a value parameter - | l² is a reference to a value parameter 16 | val o = Service() 17 | o.file = f 18 | o.log diff --git a/tests/neg-custom-args/captures/i15923.check b/tests/neg-custom-args/captures/i15923.check index 389903582bfe..44c918ff57be 100644 --- a/tests/neg-custom-args/captures/i15923.check +++ b/tests/neg-custom-args/captures/i15923.check @@ -5,12 +5,10 @@ |The leakage occurred when trying to match the following types: | |Found: (lcap: scala.caps.Capability^) ?->'s2 Cap^'s3 ->'s4 Id[Cap^'s5]^'s6 - |Required: (lcap²: scala.caps.Capability^) ?-> Cap^{lcap²} => Id[Cap^'s1]^'s7 + |Required: (lcap: scala.caps.Capability^) ?-> Cap^{lcap} => Id[Cap^'s1]^'s7 | - |where: => refers to a root capability associated with the result type of (using lcap²: scala.caps.Capability^): Cap^{lcap²} => Id[Cap^'s1]^'s7 - | ^ refers to the universal root capability - | cap is a root capability associated with the result type of (x$0: Cap^'s3): Id[Cap^'s5]^'s6 - | lcap is a reference to a value parameter - | lcap² is a reference to a value parameter + |where: => refers to a root capability associated with the result type of (using lcap: scala.caps.Capability^): Cap^{lcap} => Id[Cap^'s1]^'s7 + | ^ refers to the universal root capability + | cap is a root capability associated with the result type of (x$0: Cap^'s3): Id[Cap^'s5]^'s6 | | longer explanation available when compiling with `-explain`