Skip to content

Commit 9e1754b

Browse files
authored
Merge branch 'main' into capset-syntax3
2 parents 17df2c2 + 2712b0b commit 9e1754b

File tree

10 files changed

+145
-21
lines changed

10 files changed

+145
-21
lines changed

compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala

+11-1
Original file line numberDiff line numberDiff line change
@@ -949,7 +949,12 @@ class CheckCaptures extends Recheck, SymTransformer:
949949
// TODO follow up on this
950950
try
951951
matchParams(mdef.paramss, pt)
952-
recheckDef(mdef, mdef.symbol)
952+
capt.println(i"recheck closure block $mdef: ${mdef.symbol.infoOrCompleter}")
953+
if !mdef.symbol.isCompleted then
954+
mdef.symbol.ensureCompleted() // this will recheck def
955+
else
956+
recheckDef(mdef, mdef.symbol)
957+
953958
recheckClosure(expr, pt, forceDependent = true)
954959
finally
955960
openClosures = openClosures.tail
@@ -1112,6 +1117,10 @@ class CheckCaptures extends Recheck, SymTransformer:
11121117
finally
11131118
curEnv = saved
11141119

1120+
override def recheckTypeDef(tree: TypeDef, sym: Symbol)(using Context): Type =
1121+
try super.recheckTypeDef(tree, sym)
1122+
finally completed += sym
1123+
11151124
/** Recheck classDef by enforcing the following class-specific capture set relations:
11161125
* 1. The capture set of a class includes the capture sets of its parents.
11171126
* 2. The capture set of the self type of a class includes the capture set of the class.
@@ -1156,6 +1165,7 @@ class CheckCaptures extends Recheck, SymTransformer:
11561165
ccState.inNestedLevelUnless(cls.is(Module)):
11571166
super.recheckClassDef(tree, impl, cls)
11581167
finally
1168+
completed += cls
11591169
curEnv = saved
11601170

11611171
/** If type is of the form `T @requiresCapability(x)`,

compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala

+2-1
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,8 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe
224224
case ExtensionHasDefaultID // errorNumber: 208
225225
case FormatInterpolationErrorID // errorNumber: 209
226226
case ValueClassCannotExtendAliasOfAnyValID // errorNumber: 210
227-
case ExpectedCaptureBoundOrEqualsID // errorNumber: 211
227+
case MatchIsNotPartialFunctionID // errorNumber: 211
228+
case ExpectedCaptureBoundOrEqualsID // errorNumber: 212
228229

229230
def errorNumber = ordinal - 1
230231

compiler/src/dotty/tools/dotc/reporting/messages.scala

+32-2
Original file line numberDiff line numberDiff line change
@@ -3472,5 +3472,35 @@ end IllegalUnrollPlacement
34723472

34733473
class BadFormatInterpolation(errorText: String)(using Context) extends Message(FormatInterpolationErrorID):
34743474
def kind = MessageKind.Interpolation
3475-
def msg(using Context) = errorText
3476-
def explain(using Context) = ""
3475+
protected def msg(using Context) = errorText
3476+
protected def explain(using Context) = ""
3477+
3478+
class MatchIsNotPartialFunction(using Context) extends SyntaxMsg(MatchIsNotPartialFunctionID):
3479+
protected def msg(using Context) =
3480+
"match expression in result of block will not be used to synthesize partial function"
3481+
protected def explain(using Context) =
3482+
i"""A `PartialFunction` can be synthesized from a function literal if its body is just a pattern match.
3483+
|
3484+
|For example, `collect` takes a `PartialFunction`.
3485+
| (1 to 10).collect(i => i match { case n if n % 2 == 0 => n })
3486+
|is equivalent to using a "pattern-matching anonymous function" directly:
3487+
| (1 to 10).collect { case n if n % 2 == 0 => n }
3488+
|Compare an operation that requires a `Function1` instead:
3489+
| (1 to 10).map { case n if n % 2 == 0 => n case n => n + 1 }
3490+
|
3491+
|As a convenience, the "selector expression" of the match can be an arbitrary expression:
3492+
| List("1", "two", "3").collect(x => Try(x.toInt) match { case Success(i) => i })
3493+
|In this example, `isDefinedAt` evaluates the selector expression and any guard expressions
3494+
|in the pattern match in order to report whether an input is in the domain of the function.
3495+
|
3496+
|However, blocks of statements are not supported by this idiom:
3497+
| List("1", "two", "3").collect: x =>
3498+
| val maybe = Try(x.toInt) // statements preceding the match
3499+
| maybe match
3500+
| case Success(i) if i % 2 == 0 => i // throws MatchError on cases not covered
3501+
|
3502+
|This restriction is enforced to simplify the evaluation semantics of the partial function.
3503+
|Otherwise, it might not be clear what is computed by `isDefinedAt`.
3504+
|
3505+
|Efficient operations will use `applyOrElse` to avoid computing the match twice,
3506+
|but the `apply` body would be executed "per element" in the example."""

compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala

+7-1
Original file line numberDiff line numberDiff line change
@@ -126,10 +126,16 @@ class ExpandSAMs extends MiniPhase:
126126
// The right hand side from which to construct the partial function. This is always a Match.
127127
// If the original rhs is already a Match (possibly in braces), return that.
128128
// Otherwise construct a match `x match case _ => rhs` where `x` is the parameter of the closure.
129-
def partialFunRHS(tree: Tree): Match = tree match
129+
def partialFunRHS(tree: Tree): Match =
130+
inline def checkMatch(): Unit =
131+
tree match
132+
case Block(_, m: Match) => report.warning(reporting.MatchIsNotPartialFunction(), m.srcPos)
133+
case _ =>
134+
tree match
130135
case m: Match => m
131136
case Block(Nil, expr) => partialFunRHS(expr)
132137
case _ =>
138+
checkMatch()
133139
Match(ref(param.symbol),
134140
CaseDef(untpd.Ident(nme.WILDCARD).withType(param.symbol.info), EmptyTree, tree) :: Nil)
135141

compiler/src/dotty/tools/dotc/transform/PostTyper.scala

+4-2
Original file line numberDiff line numberDiff line change
@@ -247,8 +247,10 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase =>
247247
if sym.is(Param) then
248248
registerIfUnrolledParam(sym)
249249
// @unused is getter/setter but we want it on ordinary method params
250-
if !sym.owner.is(Method) || sym.owner.isConstructor then
251-
sym.keepAnnotationsCarrying(thisPhase, Set(defn.ParamMetaAnnot), orNoneOf = defn.NonBeanMetaAnnots)
250+
// @param should be consulted only for fields
251+
val unusing = sym.getAnnotation(defn.UnusedAnnot)
252+
sym.keepAnnotationsCarrying(thisPhase, Set(defn.ParamMetaAnnot), orNoneOf = defn.NonBeanMetaAnnots)
253+
unusing.foreach(sym.addAnnotation)
252254
else if sym.is(ParamAccessor) then
253255
// @publicInBinary is not a meta-annotation and therefore not kept by `keepAnnotationsCarrying`
254256
val publicInBinaryAnnotOpt = sym.getAnnotation(defn.PublicInBinaryAnnot)

compiler/src/dotty/tools/dotc/transform/Recheck.scala

+8-6
Original file line numberDiff line numberDiff line change
@@ -524,12 +524,14 @@ abstract class Recheck extends Phase, SymTransformer:
524524
if !skipRecheck(sym) then recheckDef(tree, sym)
525525
sym.termRef
526526
case tree: TypeDef =>
527-
// TODO: Should we allow for completers as for ValDefs or DefDefs?
528-
tree.rhs match
529-
case impl: Template =>
530-
recheckClassDef(tree, impl, sym.asClass)(using ctx.localContext(tree, sym))
531-
case _ =>
532-
recheckTypeDef(tree, sym)(using ctx.localContext(tree, sym))
527+
if !skipRecheck(sym) then
528+
// TODO: Should we allow for completers as for ValDefs or DefDefs?
529+
tree.rhs match
530+
case impl: Template =>
531+
recheckClassDef(tree, impl, sym.asClass)(using ctx.localContext(tree, sym))
532+
case _ =>
533+
recheckTypeDef(tree, sym)(using ctx.localContext(tree, sym))
534+
sym.typeRef
533535
case tree: Labeled => recheckLabeled(tree, pt)
534536

535537
def recheckUnnamed(tree: Tree, pt: Type): Type = tree match

presentation-compiler/src/main/dotty/tools/pc/PcInlineValueProvider.scala

+8-8
Original file line numberDiff line numberDiff line change
@@ -128,16 +128,13 @@ final class PcInlineValueProvider(
128128
end for
129129
end defAndRefs
130130

131-
private def stripIndentPrefix(rhs: String, refIndent: String, defIndent: String): String =
131+
private def stripIndentPrefix(rhs: String, refIndent: String, defIndent: String, hasNextLineAfterEqualsSign: Boolean): String =
132132
val rhsLines = rhs.split("\n").toList
133133
rhsLines match
134134
case h :: Nil => rhs
135135
case h :: t =>
136-
val noPrefixH = h.stripPrefix(refIndent)
137-
if noPrefixH.startsWith("{") then
138-
noPrefixH ++ t.map(refIndent ++ _.stripPrefix(defIndent)).mkString("\n","\n", "")
139-
else
140-
((" " ++ h) :: t).map(refIndent ++ _.stripPrefix(defIndent)).mkString("\n", "\n", "")
136+
val header = if !hasNextLineAfterEqualsSign then h else "\n" ++ refIndent ++ " " ++ h
137+
header ++ t.map(refIndent ++ _.stripPrefix(defIndent)).mkString("\n", "\n", "")
141138
case Nil => rhs
142139

143140
private def definitionRequiresBrackets(tree: Tree)(using Context): Boolean =
@@ -236,7 +233,7 @@ final class PcInlineValueProvider(
236233
var idx = source.startOfLine(offset)
237234
val pad = new StringBuilder
238235
while (idx != offset && idx < source.content().length && source.content()(idx).isWhitespace) {
239-
pad.append(if (idx < source.content().length && source.content()(idx) == '\t') '\t' else ' ')
236+
pad.append(source.content()(idx))
240237
idx += 1
241238
}
242239
pad.result()
@@ -262,14 +259,17 @@ final class PcInlineValueProvider(
262259
case _ => false
263260
}
264261
.map(_.fullNameBackticked)
262+
val hasNextLineAfterEqualsSign =
263+
definition.tree.sourcePos.startLine != definition.tree.rhs.sourcePos.startLine
265264
if conflictingSymbols.isEmpty then
266265
Right(
267266
Reference(
268267
occurrence.pos.toLsp,
269268
stripIndentPrefix(
270269
extendWithSurroundingParens(definition.tree.rhs.sourcePos),
271270
occurrence.tree.startPos.startColumnIndentPadding,
272-
definition.tree.startPos.startColumnIndentPadding
271+
definition.tree.startPos.startColumnIndentPadding,
272+
hasNextLineAfterEqualsSign
273273
),
274274
occurrence.parent.map(p =>
275275
RangeOffset(p.sourcePos.start, p.sourcePos.end)

presentation-compiler/test/dotty/tools/pc/tests/edit/InlineValueSuite.scala

+44
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,25 @@ class InlineValueSuite extends BaseCodeActionSuite with CommonMtagsEnrichments:
430430
)
431431

432432
@Test def `i7137a` =
433+
checkEdit(
434+
"""|def foo = {
435+
| val newValue =
436+
| val x = true
437+
| x
438+
| def bar =
439+
| val xx =new<<V>>alue
440+
|}
441+
|""".stripMargin,
442+
"""|def foo = {
443+
| def bar =
444+
| val xx =
445+
| val x = true
446+
| x
447+
|}
448+
|""".stripMargin
449+
)
450+
451+
@Test def `i7137b` =
433452
checkEdit(
434453
"""|object O {
435454
| def foo = {
@@ -454,6 +473,31 @@ class InlineValueSuite extends BaseCodeActionSuite with CommonMtagsEnrichments:
454473
|""".stripMargin
455474
)
456475

476+
@Test def `no-new-line` =
477+
checkEdit(
478+
"""|object O {
479+
| val i: Option[Int] = ???
480+
| def foo = {
481+
| val newValue = i match
482+
| case Some(x) => x
483+
| case None => 0
484+
| def bar =
485+
| val xx = new<<V>>alue
486+
| }
487+
|}
488+
|""".stripMargin,
489+
"""|object O {
490+
| val i: Option[Int] = ???
491+
| def foo = {
492+
| def bar =
493+
| val xx = i match
494+
| case Some(x) => x
495+
| case None => 0
496+
| }
497+
|}
498+
|""".stripMargin
499+
)
500+
457501
def checkEdit(
458502
original: String,
459503
expected: String,

tests/warn/i21649.scala

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//> using options -explain
2+
// do warn if function literal adaptation has match in non-empty block
3+
4+
import scala.util.*
5+
import PartialFunction.condOpt
6+
7+
val f: PartialFunction[String, Boolean] = _.toUpperCase match { case "TRUE" => true }
8+
9+
val g: PartialFunction[String, Boolean] = x =>
10+
val uc = x.toUpperCase
11+
uc match // warn
12+
case "TRUE" => true
13+
14+
def kollekt = List("1", "two", "3").collect(x => Try(x.toInt) match { case Success(i) => i })
15+
16+
def lesen = List("1", "two", "3").flatMap(x => condOpt(Try(x.toInt)) { case Success(i) => i })

tests/warn/i23033.scala

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//> using options -Werror -Wunused:all
2+
3+
import scala.annotation.unused
4+
import scala.concurrent.ExecutionContext
5+
import scala.util.NotGiven
6+
7+
object Test {
8+
given [T](using @unused ev: NotGiven[T <:< Int]): AnyRef with {}
9+
}
10+
object Useful:
11+
given [T](using @unused ec: ExecutionContext): AnyRef with {}
12+
object Syntax:
13+
given [T] => (@unused ec: ExecutionContext) => AnyRef

0 commit comments

Comments
 (0)