Skip to content

fix #10857: export from wildcard defs with same name as user declared ones #10949

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions compiler/src/dotty/tools/dotc/typer/Namer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 =>
Expand Down
29 changes: 20 additions & 9 deletions docs/docs/reference/other-new-features/export.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand All @@ -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_.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this change comes from observation that implicit forwarders are only generated from wildcard selectors and not given selectors


It is a compile-time error if a simple or renaming selector does not identify any eligible
members.
Expand All @@ -95,6 +94,8 @@ def f: c.T = ...
<a id="note_class"></a>
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
Expand All @@ -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
Expand Down
45 changes: 45 additions & 0 deletions tests/neg/i10857.scala
Original file line number Diff line number Diff line change
@@ -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
Copy link
Member Author

@bishabosha bishabosha Dec 29, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

e.g. here valueOf is still not exported from the enum because there is no user declared definition that matches that name


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))
12 changes: 12 additions & 0 deletions tests/run/i10857.scala
Original file line number Diff line number Diff line change
@@ -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)