Skip to content
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

Allow toplevel definitions #5754

Merged
merged 48 commits into from
Feb 4, 2019
Merged
Show file tree
Hide file tree
Changes from 44 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
e17ded1
Use `given` for implicit parameters and arguments
odersky Jan 30, 2019
8201ae1
Disallow empty implicit parameter sections
odersky Jan 30, 2019
5dd0a11
Avoid spurious error spans
odersky Jan 30, 2019
2baba5b
Change syntax of implicit function types and closures
odersky Jan 30, 2019
f324ea5
Fix new tests after merge
odersky Jan 30, 2019
319d874
Fix new tests after changes
odersky Jan 30, 2019
1c90ec2
Syntax change: `inferred for` instead of `instance of`
odersky Jan 30, 2019
af796f7
Reorganize docs to consolidate all implicit changes
odersky Jan 30, 2019
1e47e5c
Rename "implicit function type" to "query type"
odersky Jan 31, 2019
e792c88
Cleanups
odersky Jan 31, 2019
3bc0547
Renaming "instance of" -> "implied for"
odersky Jan 31, 2019
e2f1299
Tweaks to wordings in docs
odersky Jan 31, 2019
53f00ae
Implied Conversion -> Implicit Conversion
odersky Feb 1, 2019
a1ffafc
Rename internals to new terminologu
odersky Feb 1, 2019
c04e041
Allow toplevel definitions in syntax and parsing
odersky Dec 5, 2018
031532d
Desugar toplevel definitions
odersky Jan 19, 2019
61564ba
Treat toplevel objects as package objects
odersky Jan 20, 2019
359b80f
Make toplevel wrappers unexpressible as normal objects
odersky Jan 20, 2019
5b90688
Docs
odersky Jan 20, 2019
b069f8d
Change scheme to name package object wrappers
odersky Jan 21, 2019
ab0ea23
Allow toplevel implicits
odersky Jan 23, 2019
a105398
Disallow toplevel statements other than definitions
odersky Jan 23, 2019
97f06e0
Refine condition for printing package objects
odersky Jan 23, 2019
160fe40
Detect and report double definitions between toplevel files
odersky Jan 23, 2019
0aab6e4
Test source compiled twice
odersky Jan 23, 2019
e3a24d3
Fix package object printing
odersky Jan 23, 2019
e9dd68e
Add test for ambiguous overloads
odersky Jan 24, 2019
a4950d0
Add illegal cyclic reference test
odersky Jan 24, 2019
be77747
Add legal cyclic reference test
odersky Jan 24, 2019
560d608
Fix illegal cyclic reference test
odersky Jan 24, 2019
f9aff3e
Allow toplevel opaque types
odersky Jan 24, 2019
280744e
Update docs
odersky Jan 24, 2019
444cffa
Be more careful where we fill in missing companion objects
odersky Jan 24, 2019
e946936
Generalize member computation for package members
odersky Jan 24, 2019
67bcab2
Try various combinations of toplevel definitions
odersky Jan 24, 2019
1cb7cb1
More implicit and overloading tests
odersky Jan 24, 2019
205972c
Update docs
odersky Jan 24, 2019
44877bd
Add spec and test for private toplevel definitions
odersky Jan 24, 2019
8278568
Make toplevel privates package private
odersky Jan 24, 2019
572471f
Change scheme to widen toplevel private
odersky Jan 24, 2019
a789cdd
Skip package objects in Typer#findRef
odersky Jan 25, 2019
e81f52d
Revert #3735: Allow accesses to private package members from nested
odersky Jan 25, 2019
7b018de
Fix test check file
odersky Jan 25, 2019
4ce52d0
Tweak to prefix printing
odersky Jan 25, 2019
d874964
Try with a final modifier
odersky Jan 25, 2019
ed5304e
Address review comments
odersky Feb 2, 2019
db36559
Avoid illegal characters in package objects
odersky Feb 2, 2019
537f80d
Add separate compilation test case for #3339
odersky Feb 2, 2019
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
34 changes: 32 additions & 2 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1022,6 +1022,36 @@ object desugar {
else Apply(ref(tupleTypeRef.classSymbol.companionModule.termRef), ts)
}

/** Group all definitions that can't be at the toplebel in
Copy link
Contributor

Choose a reason for hiding this comment

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

toplebel -> toplevel

Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/** Group all definitions that can't be at the toplebel in
/** Group all definitions that can't be at the toplevel in

* an object named `<source>#object` where `<source>` is the name of the source file.
odersky marked this conversation as resolved.
Show resolved Hide resolved
* Definitions that can't be at the toplevel are:
*
* - all pattern, value and method definitions
* - non-class type definitions
* - implicit classes and objects
* - companion objects of opaque types
*/
def packageDef(pdef: PackageDef)(implicit ctx: Context): PackageDef = {
val opaqueNames = pdef.stats.collect {
case stat: TypeDef if stat.mods.is(Opaque) => stat.name
}
def needsObject(stat: Tree) = stat match {
case _: ValDef | _: PatDef | _: DefDef => true
case stat: ModuleDef =>
stat.mods.is(Implicit) || opaqueNames.contains(stat.name.stripModuleClassSuffix.toTypeName)
case stat: TypeDef => !stat.isClassDef || stat.mods.is(Implicit)
case _ => false
}
val (nestedStats, topStats) = pdef.stats.partition(needsObject)
if (nestedStats.isEmpty) pdef
else {
val sourceName = ctx.source.file.name.takeWhile(_ != '.')
odersky marked this conversation as resolved.
Show resolved Hide resolved
val groupName = (sourceName ++ str.TOPLEVEL_SUFFIX).toTermName
odersky marked this conversation as resolved.
Show resolved Hide resolved
val grouped = ModuleDef(groupName, Template(emptyConstructor, Nil, Nil, EmptyValDef, nestedStats))
cpy.PackageDef(pdef)(pdef.pid, topStats :+ grouped)
}
}

/** Make closure corresponding to function.
* params => body
* ==>
Expand Down Expand Up @@ -1086,8 +1116,8 @@ object desugar {
}

def makeContextualFunction(formals: List[Type], body: Tree)(implicit ctx: Context): Tree = {
val params = makeImplicitParameters(formals.map(TypeTree), Contextual)
new FunctionWithMods(params, body, Modifiers(Implicit | Contextual))
val params = makeImplicitParameters(formals.map(TypeTree), Given)
new FunctionWithMods(params, body, Modifiers(Implicit | Given))
}

/** Add annotation to tree:
Expand Down
6 changes: 3 additions & 3 deletions compiler/src/dotty/tools/dotc/ast/TreeInfo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -331,13 +331,13 @@ trait UntypedTreeInfo extends TreeInfo[Untyped] { self: Trees.Instance[Untyped]

/** Is `tree` an implicit function or closure, possibly nested in a block? */
def isContextualClosure(tree: Tree)(implicit ctx: Context): Boolean = unsplice(tree) match {
case tree: FunctionWithMods => tree.mods.is(Contextual)
case Function((param: untpd.ValDef) :: _, _) => param.mods.is(Contextual)
case tree: FunctionWithMods => tree.mods.is(Given)
case Function((param: untpd.ValDef) :: _, _) => param.mods.is(Given)
case Closure(_, meth, _) => true
case Block(Nil, expr) => isContextualClosure(expr)
case Block(DefDef(nme.ANON_FUN, _, params :: _, _, _) :: Nil, cl: Closure) =>
params match {
case param :: _ => param.mods.is(Contextual)
case param :: _ => param.mods.is(Given)
case Nil => cl.tpt.eq(untpd.ContextualEmptyTree) || defn.isImplicitFunctionType(cl.tpt.typeOpt)
}
case _ => false
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/ast/tpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {

def valueParam(name: TermName, origInfo: Type): TermSymbol = {
val maybeImplicit =
if (tp.isContextual) Implicit | Contextual
if (tp.isContextual) Implicit | Given
else if (tp.isImplicitMethod) Implicit
else EmptyFlags
val maybeErased = if (tp.isErasedMethod) Erased else EmptyFlags
Expand Down
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/untpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {

case class Implicit()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.ImplicitCommon)

case class Given()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.ImplicitCommon | Flags.Given)

case class Erased()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Erased)

case class Final()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Final)
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ class Definitions {
* ImplicitFunctionN traits follow this template:
*
* trait ImplicitFunctionN[T0,...,T{N-1}, R] extends Object {
* def apply with ($x0: T0, ..., $x{N_1}: T{N-1}): R
* def apply given ($x0: T0, ..., $x{N_1}: T{N-1}): R
* }
*
* ErasedFunctionN traits follow this template:
Expand All @@ -112,7 +112,7 @@ class Definitions {
* ErasedImplicitFunctionN traits follow this template:
*
* trait ErasedImplicitFunctionN[T0,...,T{N-1}, R] extends Object {
* def apply with (erased $x0: T0, ..., $x{N_1}: T{N-1}): R
* def apply given (erased $x0: T0, ..., $x{N_1}: T{N-1}): R
* }
*
* ErasedFunctionN and ErasedImplicitFunctionN erase to Function0.
Expand Down
8 changes: 4 additions & 4 deletions compiler/src/dotty/tools/dotc/core/Denotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ object Denotations {
def precedes(sym1: Symbol, sym2: Symbol) = {
def precedesIn(bcs: List[ClassSymbol]): Boolean = bcs match {
case bc :: bcs1 => (sym1 eq bc) || !(sym2 eq bc) && precedesIn(bcs1)
case Nil => true
case Nil => false
}
(sym1 ne sym2) &&
(sym1.derivesFrom(sym2) ||
Expand Down Expand Up @@ -1183,7 +1183,7 @@ object Denotations {
*/
def isDoubleDef(sym1: Symbol, sym2: Symbol)(implicit ctx: Context): Boolean =
(sym1.exists && sym2.exists &&
(sym1 ne sym2) && (sym1.owner eq sym2.owner) &&
(sym1 `ne` sym2) && (sym1.effectiveOwner `eq` sym2.effectiveOwner) &&
!sym1.is(Bridge) && !sym2.is(Bridge))

def doubleDefError(denot1: Denotation, denot2: Denotation, pre: Type = NoPrefix)(implicit ctx: Context): Nothing = {
Expand All @@ -1193,7 +1193,7 @@ object Denotations {
throw new MergeError(sym1, sym2, sym1.info, sym2.info, pre) {
override def addendum(implicit ctx: Context) =
i"""
|they are both defined in ${sym1.owner} but have matching signatures
|they are both defined in ${sym1.effectiveOwner} but have matching signatures
| ${denot1.info} and
| ${denot2.info}${super.addendum}"""
}
Expand Down Expand Up @@ -1227,7 +1227,7 @@ object Denotations {
final case class DenotUnion(denot1: PreDenotation, denot2: PreDenotation) extends MultiPreDenotation {
def exists: Boolean = true
def toDenot(pre: Type)(implicit ctx: Context): Denotation =
(denot1 toDenot pre) & (denot2 toDenot pre, pre)
denot1.toDenot(pre).&(denot2.toDenot(pre), pre)
def containsSym(sym: Symbol): Boolean =
(denot1 containsSym sym) || (denot2 containsSym sym)
type AsSeenFromResult = PreDenotation
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/core/Flags.scala
Original file line number Diff line number Diff line change
Expand Up @@ -347,8 +347,8 @@ object Flags {
/** An extension method */
final val Extension = termFlag(28, "<extension>")

/** A contextual (with) parameter */
final val Contextual = commonFlag(29, "<contextual>")
/** An inferable (`given`) parameter */
final val Given = commonFlag(29, "given")

/** Symbol is defined by a Java class */
final val JavaDefined: FlagSet = commonFlag(30, "<java>")
Expand Down
10 changes: 10 additions & 0 deletions compiler/src/dotty/tools/dotc/core/NameOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,16 @@ object NameOps {
false
}

/** is this the name of an object enclosing packagel-level definitions? */
def isPackageObjectName: Boolean = name match {
case name: TermName => name == nme.PACKAGE || name.endsWith(str.TOPLEVEL_SUFFIX)
case name: TypeName =>
name.toTermName match {
case ModuleClassName(original) => original.isPackageObjectName
case _ => false
}
}

/** Convert this module name to corresponding module class name */
def moduleClassName: TypeName = name.derived(ModuleClassName).toTypeName

Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ object StdNames {
final val INITIALIZER_PREFIX = "initial$"
final val AVOID_CLASH_SUFFIX = "$_avoid_name_clash_$"
final val MODULE_SUFFIX = "$"
final val TOPLEVEL_SUFFIX = "$package"
final val NAME_JOIN = "$"
final val DEFAULT_GETTER = "$default$"
final val LOCALDUMMY_PREFIX = "<local " // owner of local blocks
Expand Down Expand Up @@ -483,7 +484,6 @@ object StdNames {
val notifyAll_ : N = "notifyAll"
val notify_ : N = "notify"
val null_ : N = "null"
val of: N = "of"
val ofDim: N = "ofDim"
val opaque: N = "opaque"
val ordinal: N = "ordinal"
Expand Down
102 changes: 63 additions & 39 deletions compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import config.Config
import reporting.diagnostic.Message
import reporting.diagnostic.messages.BadSymbolicReference
import reporting.trace
import collection.mutable

import scala.annotation.internal.sharable

Expand Down Expand Up @@ -518,12 +519,8 @@ object SymDenotations {
name == tpnme.REFINE_CLASS

/** Is this symbol a package object or its module class? */
def isPackageObject(implicit ctx: Context): Boolean = {
val nameMatches =
if (isType) name == tpnme.PACKAGE.moduleClassName
else name == nme.PACKAGE
nameMatches && (owner is Package) && (this is Module)
}
def isPackageObject(implicit ctx: Context): Boolean =
name.isPackageObjectName && (owner is Package) && (this is Module)

/** Is this symbol an abstract type? */
final def isAbstractType(implicit ctx: Context): Boolean = this is DeferredType
Expand Down Expand Up @@ -758,9 +755,7 @@ object SymDenotations {
( !(this is Local)
|| (owner is ImplClass) // allow private local accesses to impl class members
|| isCorrectThisType(pre)
) &&
(!(this.is(Private) && owner.is(Package)) ||
owner == ctx.owner.enclosingPackageClass)
)
|| (this is Protected) &&
( superAccess
|| pre.isInstanceOf[ThisType]
Expand Down Expand Up @@ -1680,9 +1675,9 @@ object SymDenotations {
val denots1 = collect(denots, ps)
p.classSymbol.denot match {
case parentd: ClassDenotation =>
denots1 union
denots1.union(
parentd.nonPrivateMembersNamed(name)
.mapInherited(ownDenots, denots1, thisType)
.mapInherited(ownDenots, denots1, thisType))
case _ =>
denots1
}
Expand Down Expand Up @@ -1940,17 +1935,39 @@ object SymDenotations {
initPrivateWithin: Symbol)
extends ClassDenotation(symbol, ownerIfExists, name, initFlags, initInfo, initPrivateWithin) {

private[this] var packageObjCache: SymDenotation = _
private[this] var packageObjRunId: RunId = NoRunId

/** The package object in this class, of one exists */
def packageObj(implicit ctx: Context): SymDenotation = {
if (packageObjRunId != ctx.runId) {
packageObjRunId = ctx.runId
packageObjCache = NoDenotation // break cycle in case we are looking for package object itself
packageObjCache = findMember(nme.PACKAGE, thisType, EmptyFlagConjunction, EmptyFlags).asSymDenotation
private[this] var packageObjsCache: List[ClassDenotation] = _
private[this] var packageObjsRunId: RunId = NoRunId

/** The package objects in this class */
def packageObjs(implicit ctx: Context): List[ClassDenotation] = {
if (packageObjsRunId != ctx.runId) {
packageObjsRunId = ctx.runId
packageObjsCache = Nil // break cycle in case we are looking for package object itself
packageObjsCache = {
val pkgObjBuf = new mutable.ListBuffer[ClassDenotation]
for (sym <- info.decls) { // don't use filter, since that loads classes with `$`s in their name
val denot = sym.lastKnownDenotation // don't use `sym.denot`, as this brings forward classes too early
if (denot.isType && denot.name.isPackageObjectName)
pkgObjBuf += sym.asClass.classDenot
}
pkgObjBuf.toList
}
}
packageObjCache
packageObjsCache
}

/** The package object (as a term symbol) in this package that might contain
* `sym` as a member.
*/
def packageObjFor(sym: Symbol)(implicit ctx: Context): Symbol = {
val owner = sym.maybeOwner
if (owner.is(Package)) NoSymbol
else if (owner.isPackageObject) owner.sourceModule
else // owner could be class inherited by package object (until package object inheritance is removed)
packageObjs.find(_.name == packageTypeName) match {
case Some(pobj) => pobj.sourceModule
case _ => NoSymbol
}
}

/** Looks in both the package object and the package for members. The precise algorithm
Expand All @@ -1966,28 +1983,33 @@ object SymDenotations {
* object that hides a class or object in the scala package of the same name, because
* the behavior would then be unintuitive for such members.
*/
override def computeNPMembersNamed(name: Name)(implicit ctx: Context): PreDenotation =
packageObj.moduleClass.denot match {
case pcls: ClassDenotation if !pcls.isCompleting =>
if (symbol eq defn.ScalaPackageClass) {
val denots = super.computeNPMembersNamed(name)
if (denots.exists) denots else pcls.computeNPMembersNamed(name)
}
else {
val denots = pcls.computeNPMembersNamed(name)
if (denots.exists) denots else super.computeNPMembersNamed(name)
}
case _ =>
super.computeNPMembersNamed(name)
override def computeNPMembersNamed(name: Name)(implicit ctx: Context): PreDenotation = {
def recur(pobjs: List[ClassDenotation], acc: PreDenotation): PreDenotation = pobjs match {
case pcls :: pobjs1 =>
if (pcls.isCompleting) recur(pobjs1, acc)
else recur(pobjs1, acc.union(pcls.computeNPMembersNamed(name)))
case nil =>
val directMembers = super.computeNPMembersNamed(name)
if (acc.exists) acc.union(directMembers.filterWithPredicate(!_.symbol.isAbsent))
odersky marked this conversation as resolved.
Show resolved Hide resolved
else directMembers
}
if (symbol `eq` defn.ScalaPackageClass) {
val denots = super.computeNPMembersNamed(name)
if (denots.exists) denots
else recur(packageObjs, NoDenotation)
}
else recur(packageObjs, NoDenotation)
}

/** The union of the member names of the package and the package object */
override def memberNames(keepOnly: NameFilter)(implicit onBehalf: MemberNames, ctx: Context): Set[Name] = {
val ownNames = super.memberNames(keepOnly)
packageObj.moduleClass.denot match {
case pcls: ClassDenotation => ownNames union pcls.memberNames(keepOnly)
case _ => ownNames
def recur(pobjs: List[ClassDenotation], acc: Set[Name]): Set[Name] = pobjs match {
case pcls :: pobjs1 =>
recur(pobjs1, acc.union(pcls.memberNames(keepOnly)))
case nil =>
acc
}
recur(packageObjs, super.memberNames(keepOnly))
}

/** If another symbol with the same name is entered, unlink it,
Expand All @@ -1999,7 +2021,7 @@ object SymDenotations {
if (entry != null) {
if (entry.sym == sym) return false
mscope.unlink(entry)
if (sym.name == nme.PACKAGE) packageObjRunId = NoRunId
if (sym.name.isPackageObjectName) packageObjsRunId = NoRunId
}
true
}
Expand Down Expand Up @@ -2349,5 +2371,7 @@ object SymDenotations {
def baseClasses: List[ClassSymbol] = classes
}

private val packageTypeName = ModuleClassName(nme.PACKAGE).toTypeName

@sharable private[this] var indent = 0 // for completions printing
}
20 changes: 14 additions & 6 deletions compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import dotty.tools.io.{ ClassPath, ClassRepresentation, AbstractFile }
import config.Config
import Contexts._, Symbols._, Flags._, SymDenotations._, Types._, Scopes._, Names._
import NameOps._
import StdNames.str
import Decorators.{PreNamedString, StringInterpolators}
import classfile.ClassfileParser
import util.Stats
Expand Down Expand Up @@ -236,11 +237,18 @@ object SymbolLoaders {

private[core] val currentDecls: MutableScope = new PackageScope()

def isFlatName(name: SimpleName): Boolean = name.lastIndexOf('$', name.length - 2) >= 0
private def isFlatName(name: SimpleName): Boolean = {
val idx = name.lastIndexOf('$', name.length - 2)
idx >= 0 &&
(idx + str.TOPLEVEL_SUFFIX.length + 1 != name.length || !name.endsWith(str.TOPLEVEL_SUFFIX))
}

def isFlatName(classRep: ClassRepresentation): Boolean = {
val idx = classRep.name.indexOf('$')
idx >= 0 && idx < classRep.name.length - 1
/** Name of class contains `$`, excepted names ending in `$package` */
def hasFlatName(classRep: ClassRepresentation): Boolean = {
val name = classRep.name
val idx = name.lastIndexOf('$', name.length - 2)
idx >= 0 &&
(idx + str.TOPLEVEL_SUFFIX.length + 1 != name.length || !name.endsWith(str.TOPLEVEL_SUFFIX))
}

def maybeModuleClass(classRep: ClassRepresentation): Boolean = classRep.name.last == '$'
Expand All @@ -253,11 +261,11 @@ object SymbolLoaders {
val classReps = classPath.list(packageName).classesAndSources

for (classRep <- classReps)
if (!maybeModuleClass(classRep) && isFlatName(classRep) == flat &&
if (!maybeModuleClass(classRep) && hasFlatName(classRep) == flat &&
(!flat || isAbsent(classRep))) // on 2nd enter of flat names, check that the name has not been entered before
initializeFromClassPath(root.symbol, classRep)
for (classRep <- classReps)
if (maybeModuleClass(classRep) && isFlatName(classRep) == flat &&
if (maybeModuleClass(classRep) && hasFlatName(classRep) == flat &&
isAbsent(classRep))
initializeFromClassPath(root.symbol, classRep)
}
Expand Down
Loading