From f4ce8268bcb18647405c0d7e4eb4272fbc1d4b9b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 6 Dec 2021 12:53:10 +0100 Subject: [PATCH 1/5] Retain HasDefaultParams flag on export. Fixes #14020 --- compiler/src/dotty/tools/dotc/core/Flags.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Namer.scala | 1 + tests/run/i14020.check | 3 +++ tests/run/i14020.scala | 13 +++++++++++++ 4 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 tests/run/i14020.check create mode 100644 tests/run/i14020.scala diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index cb590e2384a0..817ae575581a 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -531,7 +531,7 @@ object Flags { val RetainedModuleClassFlags: FlagSet = RetainedModuleValAndClassFlags | Enum /** Flags retained in export forwarders */ - val RetainedExportFlags = Given | Implicit | Inline | Transparent + val RetainedExportFlags = Given | Implicit | Inline | Transparent | HasDefaultParams | NoDefaultParams /** Flags that apply only to classes */ val ClassOnlyFlags = Sealed | Open | Abstract.toTypeFlags diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 9398b50db5e5..cf3949b4cefe 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1116,6 +1116,7 @@ class Namer { typer: Typer => (StableRealizable, ExprType(path.tpe.select(sym))) else (EmptyFlags, mbr.info.ensureMethodic) + sym.hasDefaultParams // ensure HasDefaultParams and NoDefaultParams flags are set var mbrFlags = Exported | Method | Final | maybeStable | sym.flags & RetainedExportFlags if sym.is(ExtensionMethod) then mbrFlags |= ExtensionMethod val forwarderName = checkNoConflict(alias, isPrivate = false, span) diff --git a/tests/run/i14020.check b/tests/run/i14020.check new file mode 100644 index 000000000000..420d0f6f5bfc --- /dev/null +++ b/tests/run/i14020.check @@ -0,0 +1,3 @@ +Hello you +Hello John +Hello you diff --git a/tests/run/i14020.scala b/tests/run/i14020.scala new file mode 100644 index 000000000000..60b0caaf1a67 --- /dev/null +++ b/tests/run/i14020.scala @@ -0,0 +1,13 @@ +class A: + def greeting(name: String = "you") = s"Hello $name" + +class B: + val a = A() + export a.* + +@main def Test = + val b = B() + + println(b.a.greeting()) // works + println(b.greeting("John")) // works + println(b.greeting()) // nope ! \ No newline at end of file From 340a3b9e5f8ccbfd2c88da492c300e128a97d7d1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 7 Dec 2021 11:45:07 +0100 Subject: [PATCH 2/5] Make export of default getters work also with named and given exports --- .../src/dotty/tools/dotc/typer/Namer.scala | 60 +++++++++++++------ tests/run/i14020.check | 4 ++ tests/run/i14020.scala | 24 +++++++- 3 files changed, 68 insertions(+), 20 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index cf3949b4cefe..bdcba9b8baa9 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1070,6 +1070,26 @@ class Namer { typer: Typer => else Yes } + def defaultGetters(sym: TermSymbol): List[Symbol] = + def recur(params: List[Symbol], paramss: List[List[Symbol]], n: Int): List[Symbol] = + params match + case param :: params1 => + def otherGetters = + recur(params1, paramss, if param.isType then n else n + 1) + if param.is(HasDefault) then + val getterName = DefaultGetterName(sym.name, n) + val getter = path.tpe.member(DefaultGetterName(sym.name, n)).symbol + assert(getter.exists, i"$path does not have a default getter named $getterName") + getter :: otherGetters + else + otherGetters + case Nil => + paramss match + case params1 :: paramss1 => recur(params1, paramss1, n) + case Nil => Nil + recur(Nil, sym.paramSymss, 0) + .showing(i"default getters of $sym, ${sym.paramSymss.nestedMap(_.flagsString)} = $result") + /** Add a forwarder with name `alias` or its type name equivalent to `mbr`, * provided `mbr` is accessible and of the right implicit/non-implicit kind. */ @@ -1125,20 +1145,18 @@ class Namer { typer: Typer => forwarder.info = avoidPrivateLeaks(forwarder) forwarder.addAnnotations(sym.annotations) - val forwarderDef = - if (forwarder.isType) tpd.TypeDef(forwarder.asType) - else { - import tpd._ - val ref = path.select(sym.asTerm) - val ddef = tpd.DefDef(forwarder.asTerm, prefss => - ref.appliedToArgss(adaptForwarderParams(Nil, sym.info, prefss)) - ) - if forwarder.isInlineMethod then - PrepareInlineable.registerInlineInfo(forwarder, ddef.rhs) - ddef - } - - buf += forwarderDef.withSpan(span) + if forwarder.isType then + buf += tpd.TypeDef(forwarder.asType).withSpan(span) + else + import tpd._ + val ref = path.select(sym.asTerm) + val ddef = tpd.DefDef(forwarder.asTerm, prefss => + ref.appliedToArgss(adaptForwarderParams(Nil, sym.info, prefss))) + if forwarder.isInlineMethod then + PrepareInlineable.registerInlineInfo(forwarder, ddef.rhs) + buf += ddef.withSpan(span) + for getter <- defaultGetters(sym.asTerm) do + addForwarder(getter.name.asTermName, getter, span) end addForwarder def addForwardersNamed(name: TermName, alias: TermName, span: Span): Unit = @@ -1162,11 +1180,15 @@ class Namer { typer: Typer => def isCaseClassSynthesized(mbr: Symbol) = fromCaseClass && defn.caseClassSynthesized.contains(mbr) for mbr <- path.tpe.membersBasedOnFlags(required = EmptyFlags, excluded = PrivateOrSynthetic) do - if !mbr.symbol.isSuperAccessor && !isCaseClassSynthesized(mbr.symbol) then - // Scala 2 superaccessors have neither Synthetic nor Artfact set, so we - // need to filter them out here (by contrast, Scala 3 superaccessors are Artifacts) - // Symbols from base traits of case classes that will get synthesized implementations - // at PostTyper are also excluded. + if !mbr.symbol.isSuperAccessor + // Scala 2 superaccessors have neither Synthetic nor Artfact set, so we + // need to filter them out here (by contrast, Scala 3 superaccessors are Artifacts) + // Symbols from base traits of case classes that will get synthesized implementations + // at PostTyper are also excluded. + && !isCaseClassSynthesized(mbr.symbol) + && !mbr.symbol.name.is(DefaultGetterName) + // default getters are exported with the members they belong to + then val alias = mbr.name.toTermName if mbr.symbol.is(Given) then if !seen.contains(alias) && mbr.matchesImportBound(givenBound) then diff --git a/tests/run/i14020.check b/tests/run/i14020.check index 420d0f6f5bfc..304f6c16f72a 100644 --- a/tests/run/i14020.check +++ b/tests/run/i14020.check @@ -1,3 +1,7 @@ Hello you Hello John Hello you +Hello you +Hello John +Hello you +bark: Woof! diff --git a/tests/run/i14020.scala b/tests/run/i14020.scala index 60b0caaf1a67..3a2d9cc5a080 100644 --- a/tests/run/i14020.scala +++ b/tests/run/i14020.scala @@ -5,9 +5,31 @@ class B: val a = A() export a.* +class C: + val a = A() + export a.greeting + @main def Test = val b = B() println(b.a.greeting()) // works println(b.greeting("John")) // works - println(b.greeting()) // nope ! \ No newline at end of file + println(b.greeting()) // nope ! + + val c = C() + + println(c.a.greeting()) // works + println(c.greeting("John")) // works + println(c.greeting()) // nope ! + + val w = Wolf() + import w.given + + println(summon[String]) // error: I found: w.bark(/* missing */summon[String]) + +class Dog: + given bark(using msg: String = "Woof!"): String = s"bark: $msg" + +class Wolf: + private val dog = Dog() + export dog.given // needs to be `export dog.{given, *}` to export the default arguments From 4d87bb935f5774ac912c21d0ee283d2fbd4a0ff2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 7 Dec 2021 12:05:20 +0100 Subject: [PATCH 3/5] Simplify --- .../src/dotty/tools/dotc/typer/Namer.scala | 36 ++++++++----------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index bdcba9b8baa9..df244717f458 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1070,25 +1070,16 @@ class Namer { typer: Typer => else Yes } - def defaultGetters(sym: TermSymbol): List[Symbol] = - def recur(params: List[Symbol], paramss: List[List[Symbol]], n: Int): List[Symbol] = - params match - case param :: params1 => - def otherGetters = - recur(params1, paramss, if param.isType then n else n + 1) - if param.is(HasDefault) then - val getterName = DefaultGetterName(sym.name, n) - val getter = path.tpe.member(DefaultGetterName(sym.name, n)).symbol - assert(getter.exists, i"$path does not have a default getter named $getterName") - getter :: otherGetters - else - otherGetters - case Nil => - paramss match - case params1 :: paramss1 => recur(params1, paramss1, n) - case Nil => Nil - recur(Nil, sym.paramSymss, 0) - .showing(i"default getters of $sym, ${sym.paramSymss.nestedMap(_.flagsString)} = $result") + def foreachDefaultGetterOf(sym: TermSymbol, op: TermSymbol => Unit): Unit = + var n = 0 + for params <- sym.paramSymss; param <- params do + if param.isTerm then + if param.is(HasDefault) then + val getterName = DefaultGetterName(sym.name, n) + val getter = path.tpe.member(DefaultGetterName(sym.name, n)).symbol + assert(getter.exists, i"$path does not have a default getter named $getterName") + op(getter.asTerm) + n += 1 /** Add a forwarder with name `alias` or its type name equivalent to `mbr`, * provided `mbr` is accessible and of the right implicit/non-implicit kind. @@ -1112,6 +1103,7 @@ class Namer { typer: Typer => if canForward(mbr) == CanForward.Yes then val sym = mbr.symbol + val hasDefaults = sym.hasDefaultParams // compute here to ensure HasDefaultParams and NoDefaultParams flags are set val forwarder = if mbr.isType then val forwarderName = checkNoConflict(alias.toTypeName, isPrivate = false, span) @@ -1136,7 +1128,6 @@ class Namer { typer: Typer => (StableRealizable, ExprType(path.tpe.select(sym))) else (EmptyFlags, mbr.info.ensureMethodic) - sym.hasDefaultParams // ensure HasDefaultParams and NoDefaultParams flags are set var mbrFlags = Exported | Method | Final | maybeStable | sym.flags & RetainedExportFlags if sym.is(ExtensionMethod) then mbrFlags |= ExtensionMethod val forwarderName = checkNoConflict(alias, isPrivate = false, span) @@ -1155,8 +1146,9 @@ class Namer { typer: Typer => if forwarder.isInlineMethod then PrepareInlineable.registerInlineInfo(forwarder, ddef.rhs) buf += ddef.withSpan(span) - for getter <- defaultGetters(sym.asTerm) do - addForwarder(getter.name.asTermName, getter, span) + if hasDefaults then + foreachDefaultGetterOf(sym.asTerm, + getter => addForwarder(getter.name.asTermName, getter, span)) end addForwarder def addForwardersNamed(name: TermName, alias: TermName, span: Span): Unit = From 6fb3c531656c7ef94fdd804a76e76ba7a6ec382e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 7 Dec 2021 12:36:58 +0100 Subject: [PATCH 4/5] Fix flags of export forwarders --- compiler/src/dotty/tools/dotc/core/Flags.scala | 2 +- compiler/src/dotty/tools/dotc/core/SymDenotations.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Namer.scala | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index 817ae575581a..cb590e2384a0 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -531,7 +531,7 @@ object Flags { val RetainedModuleClassFlags: FlagSet = RetainedModuleValAndClassFlags | Enum /** Flags retained in export forwarders */ - val RetainedExportFlags = Given | Implicit | Inline | Transparent | HasDefaultParams | NoDefaultParams + val RetainedExportFlags = Given | Implicit | Inline | Transparent /** Flags that apply only to classes */ val ClassOnlyFlags = Sealed | Open | Abstract.toTypeFlags diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 7f47f1696cd6..4fff850e0be7 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -930,7 +930,7 @@ object SymDenotations { def hasDefaultParams(using Context): Boolean = if ctx.erasedTypes then false else if is(HasDefaultParams) then true - else if is(NoDefaultParams) then false + else if is(NoDefaultParams) || !is(Method) then false else val result = rawParamss.nestedExists(_.is(HasDefault)) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index df244717f458..127e03e3b47e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1128,7 +1128,9 @@ class Namer { typer: Typer => (StableRealizable, ExprType(path.tpe.select(sym))) else (EmptyFlags, mbr.info.ensureMethodic) - var mbrFlags = Exported | Method | Final | maybeStable | sym.flags & RetainedExportFlags + var flagMask = RetainedExportFlags + if sym.isTerm then flagMask |= HasDefaultParams | NoDefaultParams + var mbrFlags = Exported | Method | Final | maybeStable | sym.flags & flagMask if sym.is(ExtensionMethod) then mbrFlags |= ExtensionMethod val forwarderName = checkNoConflict(alias, isPrivate = false, span) newSymbol(cls, forwarderName, mbrFlags, mbrInfo, coord = span) From cbff98d735c350ef7860d3c3ed8d8269b76fe46c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 10 Dec 2021 18:12:07 +0100 Subject: [PATCH 5/5] Fix inline + export combination Exported functions could be inline, but that did not work at all. This should be fixed by this commit. --- compiler/src/dotty/tools/dotc/typer/Namer.scala | 2 +- tests/run/i14020.scala | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 127e03e3b47e..835c8edb2b71 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1136,7 +1136,7 @@ class Namer { typer: Typer => newSymbol(cls, forwarderName, mbrFlags, mbrInfo, coord = span) forwarder.info = avoidPrivateLeaks(forwarder) - forwarder.addAnnotations(sym.annotations) + forwarder.addAnnotations(sym.annotations.filterConserve(_.symbol != defn.BodyAnnot)) if forwarder.isType then buf += tpd.TypeDef(forwarder.asType).withSpan(span) diff --git a/tests/run/i14020.scala b/tests/run/i14020.scala index 3a2d9cc5a080..86123874c034 100644 --- a/tests/run/i14020.scala +++ b/tests/run/i14020.scala @@ -1,12 +1,15 @@ class A: def greeting(name: String = "you") = s"Hello $name" +class A2: + inline def greeting(name: String = "you") = s"Hello $name" + class B: val a = A() export a.* class C: - val a = A() + val a = A2() export a.greeting @main def Test = @@ -27,6 +30,7 @@ class C: println(summon[String]) // error: I found: w.bark(/* missing */summon[String]) + class Dog: given bark(using msg: String = "Woof!"): String = s"bark: $msg"