Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ class CommunityBuildTestC:
@Test def scalaz = projects.scalaz.run()
@Test def scas = projects.scas.run()
@Test def sconfig = projects.sconfig.run()
//@Test def shapeless3 = projects.shapeless3.run()
@Test def shapeless3 = projects.shapeless3.run()
@Test def sourcecode = projects.sourcecode.run()
@Test def specs2 = projects.specs2.run()

Expand Down
11 changes: 7 additions & 4 deletions compiler/src/dotty/tools/dotc/ast/untpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -523,7 +523,10 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
def rootDot(name: Name)(implicit src: SourceFile): Select = Select(Ident(nme.ROOTPKG), name)
def scalaDot(name: Name)(implicit src: SourceFile): Select = Select(rootDot(nme.scala), name)
def scalaAnnotationDot(name: Name)(using SourceFile): Select = Select(scalaDot(nme.annotation), name)
def scalaAnnotationInternalDot(name: Name)(using SourceFile): Select = Select(scalaAnnotationDot(nme.internal), name)
def scalaRuntimeDot(name: Name)(using SourceFile): Select = Select(scalaDot(nme.runtime), name)
def scalaCapsDot(name: Name)(using SourceFile): Select = Select(scalaDot(nme.caps), name)
def scalaCapsInternalDot(name: Name)(using SourceFile): Select = Select(scalaCapsDot(nme.internal), name)
def scalaUnit(implicit src: SourceFile): Select = scalaDot(tpnme.Unit)
def scalaAny(implicit src: SourceFile): Select = scalaDot(tpnme.Any)

Expand Down Expand Up @@ -553,16 +556,16 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
Annotated(parent, annot)

def makeReachAnnot()(using Context): Tree =
New(ref(defn.ReachCapabilityAnnot.typeRef), Nil :: Nil)
New(scalaAnnotationInternalDot(tpnme.reachCapability), Nil :: Nil)

def makeReadOnlyAnnot()(using Context): Tree =
New(ref(defn.ReadOnlyCapabilityAnnot.typeRef), Nil :: Nil)
New(scalaAnnotationInternalDot(tpnme.readOnlyCapability), Nil :: Nil)

def makeOnlyAnnot(qid: Tree)(using Context) =
New(AppliedTypeTree(ref(defn.OnlyCapabilityAnnot.typeRef), qid :: Nil), Nil :: Nil)
New(AppliedTypeTree(scalaAnnotationInternalDot(tpnme.onlyCapability), qid :: Nil), Nil :: Nil)

def makeConsumeAnnot()(using Context): Tree =
New(ref(defn.ConsumeAnnot.typeRef), Nil :: Nil)
New(scalaCapsInternalDot(tpnme.consume), Nil :: Nil)

def makeConstructor(tparams: List[TypeDef], vparamss: List[List[ValDef]], rhs: Tree = EmptyTree)(using Context): DefDef =
DefDef(nme.CONSTRUCTOR, joinParams(tparams, vparamss), TypeTree(), rhs)
Expand Down
14 changes: 14 additions & 0 deletions compiler/src/dotty/tools/dotc/cc/CCState.scala
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ class CCState:

private var collapseFresh: Boolean = false

private var discardUses: Boolean = false

object CCState:

/** If we are currently in capture checking or setup, and `mt` is a method
Expand Down Expand Up @@ -137,4 +139,16 @@ object CCState:
/** Should all FreshCap instances be treated as equal to GlobalCap? */
def collapseFresh(using Context): Boolean = ccState.collapseFresh

/** Run `op` but suppress all recording of uses in `markFree` */
inline def withDiscardedUses[T](op: => T)(using Context): T =
if isCaptureCheckingOrSetup then
val ccs = ccState
val saved = ccs.discardUses
ccs.discardUses = true
try op finally ccs.discardUses = saved
else op

/** Should uses not be recorded in markFree? */
def discardUses(using Context): Boolean = ccState.discardUses

end CCState
11 changes: 8 additions & 3 deletions compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@ object CheckCaptures:
|A classifier class is a class extending `caps.Capability` and directly extending `caps.Classifier`.""",
ann.srcPos)
check(ref)
case tpe =>
report.error(em"$elem: $tpe is not a legal element of a capture set", ann.srcPos)
case elem =>
report.error(em"$elem is not a legal element of a capture set", ann.srcPos)
ann.retainedSet.retainedElementsRaw.foreach(check)

/** Disallow bad roots anywhere in type `tp``.
Expand Down Expand Up @@ -574,7 +574,7 @@ class CheckCaptures extends Recheck, SymTransformer:
// Under deferredReaches, don't propagate out of methods inside terms.
// The use set of these methods will be charged when that method is called.

if !cs.isAlwaysEmpty then
if !cs.isAlwaysEmpty && !CCState.discardUses then
recur(cs, curEnv, null)
if addUseInfo then useInfos += ((tree, cs, curEnv))
end markFree
Expand Down Expand Up @@ -783,6 +783,9 @@ class CheckCaptures extends Recheck, SymTransformer:
else argType0.widen.stripCapturing
capt.println(i"rechecking unsafeAssumePure of $arg with $pt: $argType")
super.recheckFinish(argType, tree, pt)
else if meth == defn.Caps_unsafeDiscardUses then
val arg :: Nil = tree.args: @unchecked
withDiscardedUses(recheck(arg, pt))
else
val res = super.recheckApply(tree, pt)
includeCallCaptures(meth, res, tree)
Expand Down Expand Up @@ -837,6 +840,8 @@ class CheckCaptures extends Recheck, SymTransformer:
appType match
case appType @ CapturingType(appType1, refs)
if qualType.exists
&& !qualType.isBoxedCapturing
&& !resultType.isBoxedCapturing
&& !tree.fun.symbol.isConstructor
&& !resultType.captureSet.containsResultCapability
&& qualCaptures.mightSubcapture(refs)
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1020,6 +1020,7 @@ class Definitions {
@tu lazy val CapsUnsafeModule: Symbol = requiredModule("scala.caps.unsafe")
@tu lazy val Caps_unsafeAssumePure: Symbol = CapsUnsafeModule.requiredMethod("unsafeAssumePure")
@tu lazy val Caps_unsafeAssumeSeparate: Symbol = CapsUnsafeModule.requiredMethod("unsafeAssumeSeparate")
@tu lazy val Caps_unsafeDiscardUses: Symbol = CapsUnsafeModule.requiredMethod("unsafeDiscardUses")
@tu lazy val Caps_unsafeErasedValue: Symbol = CapsUnsafeModule.requiredMethod("unsafeErasedValue")
@tu lazy val Caps_ContainsTrait: TypeSymbol = CapsModule.requiredType("Contains")
@tu lazy val Caps_ContainsModule: Symbol = requiredModule("scala.caps.Contains")
Expand Down
3 changes: 3 additions & 0 deletions compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,7 @@ object StdNames {
val ofDim: N = "ofDim"
val on: N = "on"
val only: N = "only"
val onlyCapability: N = "onlyCapability"
val opaque: N = "opaque"
val open: N = "open"
val ordinal: N = "ordinal"
Expand All @@ -591,6 +592,8 @@ object StdNames {
val productPrefix: N = "productPrefix"
val quotes : N = "quotes"
val raw_ : N = "raw"
val reachCapability: N = "reachCapability"
val readOnlyCapability: N = "readOnlyCapability"
val rd: N = "rd"
val refl: N = "refl"
val reflect: N = "reflect"
Expand Down
98 changes: 45 additions & 53 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4288,72 +4288,64 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
val allDenots = ref.denot.alternatives
if pt.isExtensionApplyProto then allDenots.filter(_.symbol.is(ExtensionMethod))
else allDenots
def altRef(alt: SingleDenotation) = TermRef(ref.prefix, ref.name, alt)
val alts = altDenots.map(altRef)

typr.println(i"adapt overloaded $ref with alternatives ${altDenots map (_.info)}%\n\n %")

/** Search for an alternative that does not take parameters.
* If there is one, return it, otherwise return the error tree.
*/
def tryParameterless(alts: List[TermRef])(error: => tpd.Tree): Tree =
def tryParameterless(error: => tpd.Tree): Tree =
alts.filter(_.info.isParameterless) match
case alt :: Nil => readaptSimplified(tree.withType(alt))
case _ =>
altDenots.find(_.info.paramInfoss == ListOfNil) match
case Some(alt) => readaptSimplified(tree.withType(alt.symbol.denot.termRef))
case Some(alt) => readaptSimplified(tree.withType(altRef(alt)))
case _ => error

def altRef(alt: SingleDenotation) = TermRef(ref.prefix, ref.name, alt)
val alts = altDenots.map(altRef)

resolveOverloaded(alts, pt) match
case alt :: Nil =>
readaptSimplified(tree.withType(alt))
case Nil =>
// If no alternative matches, there are still two ways to recover:
// 1. If context is an application, try to insert an apply or implicit
// 2. If context is not an application, pick a alternative that does
// not take parameters.

def errorNoMatch = errorTree(tree, NoMatchingOverload(altDenots, pt))

pt match
case pt: FunOrPolyProto if pt.applyKind != ApplyKind.Using =>
// insert apply or convert qualifier, but only for a regular application
tryInsertApplyOrImplicit(tree, pt, locked)(errorNoMatch)
case _ =>
tryParameterless(alts)(errorNoMatch)

case ambiAlts =>
// If there are ambiguous alternatives, and:
// 1. the types aren't erroneous
// 2. the expected type is not a function type
// 3. there exist a parameterless alternative
//
// Then, pick the parameterless alternative.
// See tests/pos/i10715-scala and tests/pos/i10715-java.

/** Constructs an "ambiguous overload" error */
def errorAmbiguous =
val remainingDenots = altDenots.filter(denot => ambiAlts.contains(altRef(denot)))
val addendum =
if ambiAlts.exists(!_.symbol.exists) then
i"""|
|
|Note: Overloaded definitions introduced by refinements cannot be resolved"""
else ""
errorTree(tree, AmbiguousOverload(tree, remainingDenots, pt, addendum))
end errorAmbiguous

if tree.tpe.isErroneous || pt.isErroneous then
tree.withType(UnspecifiedErrorType)
else
pt match
case _: FunProto =>
errorAmbiguous
case _ =>
tryParameterless(alts)(errorAmbiguous)

end match
case alt :: Nil =>
readaptSimplified(tree.withType(alt))
case Nil =>
// If no alternative matches, there are still two ways to recover:
// 1. If context is an application, try to insert an apply or implicit
// 2. If context is not an application, pick an alternative that does
// not take parameters.
def errorNoMatch = errorTree(tree, NoMatchingOverload(altDenots, pt))

pt match
case pt: FunOrPolyProto if pt.applyKind != ApplyKind.Using =>
// insert apply or convert qualifier, but only for a regular application
tryInsertApplyOrImplicit(tree, pt, locked)(errorNoMatch)
case _ =>
tryParameterless(errorNoMatch)
case ambiAlts =>
// If there are ambiguous alternatives, and:
// 1. the types aren't erroneous
// 2. the expected type is not a function type
// 3. there exists a parameterless alternative
//
// Then, pick the parameterless alternative. See tests/pos/i10715-*

/** Constructs an "ambiguous overload" error */
def errorAmbiguous =
val remainingDenots = altDenots.filter(denot => ambiAlts.contains(altRef(denot)))
val addendum =
if ambiAlts.exists(!_.symbol.exists) then
i"""|
|
|Note: Overloaded definitions introduced by refinements cannot be resolved"""
else ""
errorTree(tree, AmbiguousOverload(tree, remainingDenots, pt, addendum))

pt match
case pt if tree.tpe.isErroneous || pt.isErroneous =>
tree.withType(UnspecifiedErrorType)
case _: FunProto =>
errorAmbiguous
case _ =>
tryParameterless(errorAmbiguous)
end adaptOverloaded

def adaptToArgs(wtp: Type, pt: FunProto): Tree = wtp match {
Expand Down
3 changes: 3 additions & 0 deletions library/src/scala/caps/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,9 @@ object unsafe:
*/
def unsafeAssumeSeparate(op: Any): op.type = op

/** A wrapper around code for which uses go unrecorded */
def unsafeDiscardUses(op: Any): op.type = op

/** An unsafe variant of erasedValue that can be used as an escape hatch. Unlike the
* user-accessible compiletime.erasedValue, this version is assumed
* to be a pure expression, hence capability safe. But there is no proof
Expand Down
19 changes: 16 additions & 3 deletions library/src/scala/collection/immutable/LazyListIterable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1264,13 +1264,26 @@ object LazyListIterable extends IterableFactory[LazyListIterable] {
if (it.hasNext) eagerCons(it.next(), newLL(eagerHeadFromIterator(it)))
else Empty

// TODO This should be (xss: (collection.Iterable[A]^)*)
override def concat[A](xss: collection.Iterable[A]*): LazyListIterable[A] =
if (xss.knownSize == 0) empty
else newLL(eagerHeadConcatIterators(xss.iterator))

private def eagerHeadConcatIterators[A](it: Iterator[collection.Iterable[A]^]^): LazyListIterable[A]^{it} =
if (!it.hasNext) Empty
else eagerHeadPrependIterator(it.next().iterator)(eagerHeadConcatIterators(it))
/* TODO This should be:
private def eagerHeadConcatIterators[A](it: Iterator[collection.Iterable[A]^]^): LazyListIterable[A]^{it*} =
if !it.hasNext then Empty
else
eagerHeadPrependIterator
(caps.unsafe.unsafeDiscardUses(it.next()).iterator)
(eagerHeadConcatIterators(it))
*/

private def eagerHeadConcatIterators[A](it: Iterator[collection.Iterable[A]]^): LazyListIterable[A]^{it} =
if !it.hasNext then Empty
else
eagerHeadPrependIterator
(it.next().iterator)
(eagerHeadConcatIterators(it))

/** An infinite LazyListIterable that repeatedly applies a given function to a start value.
*
Expand Down
4 changes: 2 additions & 2 deletions project/Build.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1960,7 +1960,7 @@ object Build {
"org.scala-lang.modules" % "scala-asm" % "9.9.0-scala-1",
Dependencies.compilerInterface,
"com.github.sbt" % "junit-interface" % "0.13.3" % Test,
("io.get-coursier" %% "coursier" % "2.0.16" % Test).cross(CrossVersion.for3Use2_13),
("io.get-coursier" %% "coursier" % "2.0.16" % Test).cross(CrossVersion.for3Use2_13).excludeAll(("org.scala-lang" % "scala-reflect")),
),
// NOTE: The only difference here is that we drop `-Werror` and semanticDB for now
Compile / scalacOptions := Seq("-deprecation", "-feature", "-unchecked", "-encoding", "UTF8", "-language:implicitConversions"),
Expand Down Expand Up @@ -3028,7 +3028,7 @@ object Build {
publishConfiguration ~= (_.withOverwrite(true)),
publishLocalConfiguration ~= (_.withOverwrite(true)),
projectID ~= {id =>
val line = "scala.versionLine" -> versionLine
val line = "info.scala.versionLine" -> versionLine
id.withExtraAttributes(id.extraAttributes + line)
},
Test / publishArtifact := false,
Expand Down
46 changes: 46 additions & 0 deletions tests/neg-custom-args/captures/apply-rule.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/apply-rule.scala:3:19 ------------------------------------
3 | val _: () -> A = x // error
| ^
| Found: (x : () ->{s*} A^{})
| Required: () -> A
|
| Note that capability s* is not included in capture set {}.
|
| longer explanation available when compiling with `-explain`
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/apply-rule.scala:5:19 ------------------------------------
5 | val _: () -> A = y // error
| ^
| Found: (y : () ->{s*} A^{})
| Required: () -> A
|
| Note that capability s* is not included in capture set {}.
|
| longer explanation available when compiling with `-explain`
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/apply-rule.scala:9:19 ------------------------------------
9 | val _: () -> A = x // error
| ^
| Found: (x : () ->{C} A^{})
| Required: () -> A
|
| Note that capability C is not included in capture set {}.
|
| longer explanation available when compiling with `-explain`
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/apply-rule.scala:11:19 -----------------------------------
11 | val _: () -> A = y // error
| ^
| Found: (y : () ->{C} A^{})
| Required: () -> A
|
| Note that capability C is not included in capture set {}.
|
| longer explanation available when compiling with `-explain`
-- Error: tests/neg-custom-args/captures/apply-rule.scala:2:11 ---------------------------------------------------------
2 | val x = s(0) // error
| ^^^^
| Local reach capability s* leaks into capture scope of method select.
| You could try to abstract the capabilities referred to by s* in a capset variable.
-- Error: tests/neg-custom-args/captures/apply-rule.scala:4:12 ---------------------------------------------------------
4 | val y = s.head // error
| ^^^^^^
| Local reach capability s* leaks into capture scope of method select.
| You could try to abstract the capabilities referred to by s* in a capset variable.
16 changes: 16 additions & 0 deletions tests/neg-custom-args/captures/apply-rule.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
def select[A](s: Seq[() => A]) =
val x = s(0) // error
val _: () -> A = x // error
val y = s.head // error
val _: () -> A = y // error

def select2[A, C^](s: Seq[() ->{C} A]) =
val x = s(0)
val _: () -> A = x // error
val y = s.head
val _: () -> A = y // error





12 changes: 12 additions & 0 deletions tests/neg-custom-args/captures/nicolas1.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/nicolas1.scala:10:2 --------------------------------------
10 | val all: Seq[Rand ?->{head, tail*} A] = head +: tail // error
| ^
| Found: (contextual$1: Rand^'s1) ?->{head, tail*} A^'s2
| Required: (Rand^) ?->{head} A
|
| Note that capability tail* is not included in capture set {head}.
|
| where: ^ refers to the universal root capability
11 | all(nextInt(all.length))
|
| longer explanation available when compiling with `-explain`
11 changes: 11 additions & 0 deletions tests/neg-custom-args/captures/nicolas1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import caps.*

trait Rand extends SharedCapability:
def range(min: Int, max: Int): Int

def nextInt(max: Int): Rand ?-> Int =
r ?=> r.range(0, max)

def oneOf[A](head: Rand ?=> A, tail: (Rand ?=> A)*): Rand ?->{head} A =
val all: Seq[Rand ?->{head, tail*} A] = head +: tail // error
all(nextInt(all.length))
2 changes: 2 additions & 0 deletions tests/pos-custom-args/captures/discardUses.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def test(c: Object^) =
val x: () -> Unit = caps.unsafe.unsafeDiscardUses(() => println(c))
Loading
Loading