diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index ef1f0d225d1a..731f1786cac8 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1086,15 +1086,24 @@ class Namer { typer: Typer => } } + def addWildcardForwardersNamed(name: TermName, span: Span): Unit = + List(name, name.toTypeName) + .flatMap(path.tpe.memberBasedOnFlags(_, excluded = Private|Given|ConstructorProxy).alternatives) + .foreach(addForwarder(name, _, span)) // ignore if any are not added + def addWildcardForwarders(seen: List[TermName], span: Span): Unit = + val nonContextual = mutable.HashSet(seen: _*) for mbr <- path.tpe.membersBasedOnFlags(required = EmptyFlags, excluded = PrivateOrSynthetic) do if !mbr.symbol.isSuperAccessor 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) val alias = mbr.name.toTermName - if !seen.contains(alias) - && mbr.matchesImportBound(if mbr.symbol.is(Given) then givenBound else wildcardBound) - then addForwarder(alias, mbr, span) + if mbr.symbol.is(Given) then + if !seen.contains(alias) && mbr.matchesImportBound(givenBound) then + addForwarder(alias, mbr, span) + else if !nonContextual.contains(alias) && mbr.matchesImportBound(wildcardBound) then + nonContextual += alias + addWildcardForwardersNamed(alias, span) def addForwarders(sels: List[untpd.ImportSelector], seen: List[TermName]): Unit = sels match case sel :: sels1 => diff --git a/docs/docs/reference/other-new-features/export.md b/docs/docs/reference/other-new-features/export.md index 9d9e3eecfa9f..2a7aaada28ef 100644 --- a/docs/docs/reference/other-new-features/export.md +++ b/docs/docs/reference/other-new-features/export.md @@ -47,7 +47,6 @@ An export clause has the same format as an import clause. Its general form is: ```scala export path . { sel_1, ..., sel_n } -export given path . { sel_1, ..., sel_n } ``` It consists of a qualifier expression `path`, which must be a stable identifier, followed by @@ -58,7 +57,8 @@ of one of the following forms: - A _renaming selector_ `x => y` creates aliases for all eligible members of `path` that are named `x`, but the alias is named `y` instead of `x`. - An _omitting selector_ `x => _` prevents `x` from being aliased by a subsequent wildcard selector. - - A _wildcard selector_ creates aliases for all eligible members of `path` except for + - A _given selector_ `given x` has an optional type bound `x`. It creates aliases for all eligible given instances that conform to either `x`, or `Any` if `x` is omitted, except for members that are named by a previous simple, renaming, or omitting selector. + - A _wildcard selector_ `_` creates aliases for all eligible members of `path` except for given instances, synthetic members generated by the compiler and those members that are named by a previous simple, renaming, or omitting selector. A member is _eligible_ if all of the following holds: @@ -68,8 +68,7 @@ A member is _eligible_ if all of the following holds: a base class of the class containing the export clause. - it is accessible at the export clause, - it is not a constructor, nor the (synthetic) class part of an object, - - it is a given instance (or an old-style `implicit` value) - if and only if the export is tagged with `given`. + - it is a given instance (declared with `given`) if and only if the export is from a _given selector_. It is a compile-time error if a simple or renaming selector does not identify any eligible members. @@ -95,6 +94,8 @@ def f: c.T = ... Export clauses can appear in classes or they can appear at the top-level. An export clause cannot appear as a statement in a block. +If an export clause contains a wildcard or given selector, it is forbidden for its qualifier path to refer to a package. This is because it is not yet known how to safely track wildcard dependencies to a package for the purposes of incremental compilation. + (\*) **Note:** Unless otherwise stated, the term "class" in this discussion also includes object and trait definitions. ## Motivation @@ -111,11 +112,21 @@ more flexible way. ## Syntax changes: ``` -TemplateStat ::= ... - | Export -TopStat ::= ... - | Export -Export ::= ‘export’ [‘given’] ImportExpr {‘,’ ImportExpr} +TemplateStat ::= ... + | Export +TopStat ::= ... + | Export +Import ::= ‘import’ ImportExpr {‘,’ ImportExpr} +ImportExpr ::= StableId ‘.’ ImportSpec +ImportSpec ::= id + | ‘_’ + | ‘given’ + | ‘{’ ImportSelectors) ‘}’ +ImportSelectors ::= id [‘=>’ id | ‘=>’ ‘_’] [‘,’ ImportSelectors] + | WildCardSelector {‘,’ WildCardSelector} +WildCardSelector ::= ‘_' + | ‘given’ [InfixType] +Export ::= ‘export’ ImportExpr {‘,’ ImportExpr} ``` ## Elaboration of Export Clauses diff --git a/tests/neg/i10857.scala b/tests/neg/i10857.scala new file mode 100644 index 000000000000..b9128933484f --- /dev/null +++ b/tests/neg/i10857.scala @@ -0,0 +1,45 @@ +object Module: + + class Bar + class Baz + class Qux + + object Givens: + given GivenBar: Bar = new Bar() + def GivenBar(ignored: Int): Bar = new Bar() + class GivenBar + + object Members: + given Member: Baz = new Baz() + private def Member(ignored1: String)(ignored2: Int): Bar = new Bar() + def Member(ignored: Int): Baz = new Baz() + class Member + + object Combined: + given GivenQux: Qux = new Qux() + def GivenQux(ignored: Int): Qux = new Qux() + + enum Color: + case Red, Green, Blue + + export Color._ // will only export synthetic defs with same name as standard definition + export Givens.given // should only export given values + export Members._ // should only export values that are not given + export Combined.{_, given} // should only export values that are not given + +@main def Test = + + println(Module.Red) + println(Module.valueOf("Red")) // error: value valueOf is not a member + + println(summon[Module.Bar]) + println(new Module.GivenBar()) // error: type GivenBar is not a member + println(Module.GivenBar(23)) // error: method GivenBar does not take parameters + + println(new Module.Member()) + println(Module.Member(23)) + println(Module.Member("?")(23)) // error: Found: ("?" : String) Required: Int + println(summon[Module.Baz]) // error: no implicit argument of type Module.Baz was found + + println(summon[Module.Qux]) + println(Module.GivenQux(23)) diff --git a/tests/run/i10857.scala b/tests/run/i10857.scala new file mode 100644 index 000000000000..c8b28d04651e --- /dev/null +++ b/tests/run/i10857.scala @@ -0,0 +1,12 @@ +object Module: + + enum Foo: + case Value + case Parameterised(i: Int) + + export Foo._ + +@main def Test = + import Module.given + println(Module.Parameterised.apply(23)) // synthetic companion object is exported + println(Module.Value)