diff --git a/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala b/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala index 1a7591794712..ae1217635662 100644 --- a/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala +++ b/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala @@ -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() diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 17dbb5bff213..bad70cb3a01c 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -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) @@ -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) diff --git a/compiler/src/dotty/tools/dotc/cc/CCState.scala b/compiler/src/dotty/tools/dotc/cc/CCState.scala index 061810c3cd42..9d63be99842a 100644 --- a/compiler/src/dotty/tools/dotc/cc/CCState.scala +++ b/compiler/src/dotty/tools/dotc/cc/CCState.scala @@ -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 @@ -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 diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index cca9814c00b1..9d17f66f1dd8 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -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``. @@ -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 @@ -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) @@ -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) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 6eca9ed4d4bc..9005986b37f1 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -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") diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 323c59a5711d..3213be389c9d 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -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" @@ -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" diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 80fe71328467..464fbec1750c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -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 { diff --git a/library/src/scala/caps/package.scala b/library/src/scala/caps/package.scala index ae10018eb247..0527c3e149d6 100644 --- a/library/src/scala/caps/package.scala +++ b/library/src/scala/caps/package.scala @@ -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 diff --git a/library/src/scala/collection/immutable/LazyListIterable.scala b/library/src/scala/collection/immutable/LazyListIterable.scala index dd0ec12a14f6..88438f35d1ff 100644 --- a/library/src/scala/collection/immutable/LazyListIterable.scala +++ b/library/src/scala/collection/immutable/LazyListIterable.scala @@ -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. * diff --git a/project/Build.scala b/project/Build.scala index 1b83180779aa..8b4f1ea6c188 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -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"), @@ -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, diff --git a/tests/neg-custom-args/captures/apply-rule.check b/tests/neg-custom-args/captures/apply-rule.check new file mode 100644 index 000000000000..73665fc79fdf --- /dev/null +++ b/tests/neg-custom-args/captures/apply-rule.check @@ -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. diff --git a/tests/neg-custom-args/captures/apply-rule.scala b/tests/neg-custom-args/captures/apply-rule.scala new file mode 100644 index 000000000000..4a8ff1909dd6 --- /dev/null +++ b/tests/neg-custom-args/captures/apply-rule.scala @@ -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 + + + + + diff --git a/tests/neg-custom-args/captures/nicolas1.check b/tests/neg-custom-args/captures/nicolas1.check new file mode 100644 index 000000000000..ec8700133439 --- /dev/null +++ b/tests/neg-custom-args/captures/nicolas1.check @@ -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` diff --git a/tests/neg-custom-args/captures/nicolas1.scala b/tests/neg-custom-args/captures/nicolas1.scala new file mode 100644 index 000000000000..ff592e74a1ca --- /dev/null +++ b/tests/neg-custom-args/captures/nicolas1.scala @@ -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)) \ No newline at end of file diff --git a/tests/pos-custom-args/captures/discardUses.scala b/tests/pos-custom-args/captures/discardUses.scala new file mode 100644 index 000000000000..37a2b85bef63 --- /dev/null +++ b/tests/pos-custom-args/captures/discardUses.scala @@ -0,0 +1,2 @@ +def test(c: Object^) = + val x: () -> Unit = caps.unsafe.unsafeDiscardUses(() => println(c)) diff --git a/tests/pos/i24631/TestJava.java b/tests/pos/i24631/TestJava.java new file mode 100644 index 000000000000..1a869cbdd6db --- /dev/null +++ b/tests/pos/i24631/TestJava.java @@ -0,0 +1,12 @@ +package example; + +public abstract class TestJava { + public abstract T create(String foo); + + // Note that this is the method that's called from Scala code + public T create() { return create(""); } + + public static class Concrete extends TestJava { + @Override public String create(String foo) { return foo; } + } +} diff --git a/tests/pos/i24631/test.scala b/tests/pos/i24631/test.scala new file mode 100644 index 000000000000..c54eb7f98c69 --- /dev/null +++ b/tests/pos/i24631/test.scala @@ -0,0 +1,2 @@ +val s = new example.TestJava.Concrete().create +val s2: String = s diff --git a/tests/pos/i24631b.scala b/tests/pos/i24631b.scala new file mode 100644 index 000000000000..9e408729c6c3 --- /dev/null +++ b/tests/pos/i24631b.scala @@ -0,0 +1,11 @@ +//> using options -source:3.0-migration + +abstract class C[A]: + def create(s: String): A + def create(): A = create("") + +class D extends C[String]: + def create(s: String): String = s + +val s = D().create +val s2: String = s