diff --git a/effekt/shared/src/main/scala/effekt/Namer.scala b/effekt/shared/src/main/scala/effekt/Namer.scala index 844f88370..ad4a8a11e 100644 --- a/effekt/shared/src/main/scala/effekt/Namer.scala +++ b/effekt/shared/src/main/scala/effekt/Namer.scala @@ -704,6 +704,15 @@ object Namer extends Phase[Parsed, NameResolved] { patterns.flatMap { resolve } case source.MultiPattern(patterns, _) => patterns.flatMap { resolve } + case source.OrPattern(patterns, _) => + patterns.foreach { p => + val bindings = resolve(p) + if (bindings.nonEmpty) { + val bs = bindings.map { b => pretty"`${b.name}`" }.mkString(", ") + Context.error(pretty"Pattern ${p} binds ${bs}, but or-patterns should not bind values.") + } + } + Nil } def resolve(p: source.MatchGuard)(using Context): Unit = p match { diff --git a/effekt/shared/src/main/scala/effekt/Parser.scala b/effekt/shared/src/main/scala/effekt/Parser.scala index 8aa94351f..7bc519db3 100644 --- a/effekt/shared/src/main/scala/effekt/Parser.scala +++ b/effekt/shared/src/main/scala/effekt/Parser.scala @@ -910,7 +910,7 @@ class Parser(positions: Positions, tokens: Seq[Token], source: Source) { def matchClause(): MatchClause = nonterminal: - val patterns = `case` ~> some(matchPattern, `,`) + val patterns = `case` ~> some(orPattern, `,`) val pattern: MatchPattern = patterns match { case Many(List(pat), _) => pat case pats => MultiPattern(pats.unspan, pats.span) @@ -930,25 +930,39 @@ class Parser(positions: Positions, tokens: Seq[Token], source: Source) { def matchGuard(): MatchGuard = nonterminal: - expr() ~ when(`is`) { Some(matchPattern()) } { None } match { + expr() ~ when(`is`) { Some(orPattern()) } { None } match { case e ~ Some(p) => MatchGuard.PatternGuard(e, p, span()) case e ~ None => MatchGuard.BooleanGuard(e, span()) } + // TODO check positions and potentially change syntax + def orPattern(): MatchPattern = + nonterminal: + val subpatterns = ListBuffer.empty[MatchPattern] + subpatterns.addOne(matchPattern()) + while (peek.kind == TokenKind.`|`) { + next() + subpatterns.addOne(matchPattern()) + } + subpatterns.toList match { + case p :: Nil => p + case ps => OrPattern(ps, span()) + } + def matchPattern(): MatchPattern = nonterminal: peek.kind match { case `__` => skip(); IgnorePattern(span()) case _ if isVariable => idRef() match { - case id if peek(`(`) => TagPattern(id, many(matchPattern, `(`, `,`, `)`).unspan, span()) + case id if peek(`(`) => TagPattern(id, many(orPattern, `(`, `,`, `)`).unspan, span()) case IdRef(Nil, name, span) => AnyPattern(IdDef(name, span), span) case IdRef(_, name, _) => fail("Cannot use qualified names to bind a pattern variable") } case _ if isVariable => AnyPattern(idDef(), span()) case _ if isLiteral => LiteralPattern(literal(), span()) - case `(` => some(matchPattern, `(`, `,`, `)`) match { + case `(` => some(orPattern, `(`, `,`, `)`) match { case Many(p :: Nil , _) => fail("Pattern matching on tuples requires more than one element") case Many(ps, _) => TagPattern(IdRef(List("effekt"), s"Tuple${ps.size}", span().synthesized), ps, span()) } diff --git a/effekt/shared/src/main/scala/effekt/Typer.scala b/effekt/shared/src/main/scala/effekt/Typer.scala index 61ccf6773..a42c953a4 100644 --- a/effekt/shared/src/main/scala/effekt/Typer.scala +++ b/effekt/shared/src/main/scala/effekt/Typer.scala @@ -609,8 +609,13 @@ object Typer extends Phase[NameResolved, Typechecked] { } bindings + case source.MultiPattern(patterns, _) => Context.panic("Multi-pattern should have been split at the match and not occur nested.") + + case source.OrPattern(patterns, _) => + patterns.foreach { p => checkPattern(sc, p) } + Map.empty } match { case res => Context.annotateInferredType(pattern, sc); res } def checkGuard(guard: MatchGuard)(using Context, Captures): Result[Map[Symbol, ValueType]] = guard match { diff --git a/effekt/shared/src/main/scala/effekt/core/PatternMatchingCompiler.scala b/effekt/shared/src/main/scala/effekt/core/PatternMatchingCompiler.scala index 04ae6f906..9e1ccf319 100644 --- a/effekt/shared/src/main/scala/effekt/core/PatternMatchingCompiler.scala +++ b/effekt/shared/src/main/scala/effekt/core/PatternMatchingCompiler.scala @@ -224,9 +224,17 @@ object PatternMatchingCompiler { core.Match(scrutinee, branches, default) } + // (3c) Split or-patterns + def splitOnOr(ps: List[Pattern]) = + val Clause(Condition.Patterns(patterns) :: rest, target, targs, args) = headClause : @unchecked + compile(ps.map { p => + Clause(Condition.Patterns(patterns + (scrutinee -> p)) :: rest, target, targs, args) + } ++ remainingClauses) + patterns(scrutinee) match { case Pattern.Literal(lit, equals) => splitOnLiteral(lit, equals) case p: Pattern.Tag => splitOnTag() + case Pattern.Or(ps) => splitOnOr(ps) case _ => ??? } } @@ -278,9 +286,11 @@ object PatternMatchingCompiler { case Condition.Patterns(other) :: rest => val substituted = other.map(substitute) val additionalSubst = substituted.collect { case (sc, Pattern.Any(id)) => id -> sc } - val filtered = substituted.collect { - case (sc, p: Pattern.Tag) => sc -> p - case (sc, p: Pattern.Literal) => sc -> p + val filtered = substituted.flatMap { + case (sc, p: Pattern.Tag) => Some(sc -> p) + case (sc, p: Pattern.Literal) => Some(sc -> p) + case (sc, p: Pattern.Or) => Some(sc -> p) + case (sc, Pattern.Ignore() | Pattern.Any(_)) => None } normalize(patterns ++ filtered, rest, substitution ++ additionalSubst) diff --git a/effekt/shared/src/main/scala/effekt/core/Transformer.scala b/effekt/shared/src/main/scala/effekt/core/Transformer.scala index 23b6f762c..1ad5253d6 100644 --- a/effekt/shared/src/main/scala/effekt/core/Transformer.scala +++ b/effekt/shared/src/main/scala/effekt/core/Transformer.scala @@ -670,6 +670,7 @@ object Transformer extends Phase[Typechecked, CoreTransformed] { case source.TagPattern(id, patterns, _) => patterns.flatMap(boundInPattern) case _: source.LiteralPattern | _: source.IgnorePattern => Nil case source.MultiPattern(patterns, _) => patterns.flatMap(boundInPattern) + case source.OrPattern(patterns, _) => Nil } def boundInGuard(g: source.MatchGuard): List[core.ValueParam] = g match { case MatchGuard.BooleanGuard(condition, _) => Nil @@ -680,6 +681,7 @@ object Transformer extends Phase[Typechecked, CoreTransformed] { case p @ source.TagPattern(id, patterns, _) => Context.annotation(Annotations.TypeParameters, p) ++ patterns.flatMap(boundTypesInPattern) case _: source.LiteralPattern | _: source.IgnorePattern => Nil case source.MultiPattern(patterns, _) => patterns.flatMap(boundTypesInPattern) + case source.OrPattern(patterns, _) => Nil } def boundTypesInGuard(g: source.MatchGuard): List[Id] = g match { case MatchGuard.BooleanGuard(condition, _) => Nil @@ -718,6 +720,8 @@ object Transformer extends Phase[Typechecked, CoreTransformed] { Pattern.Literal(Literal(value, transform(tpe)), equalsFor(tpe)) case source.MultiPattern(patterns, _) => Context.panic("Multi-pattern should have been split on toplevel / nested MultiPattern") + case source.OrPattern(pattern, _) => + Pattern.Or(pattern.map(transformPattern)) } def transformGuard(p: source.MatchGuard): List[Condition] = diff --git a/effekt/shared/src/main/scala/effekt/source/Tree.scala b/effekt/shared/src/main/scala/effekt/source/Tree.scala index 86e39bdb8..7aada779a 100644 --- a/effekt/shared/src/main/scala/effekt/source/Tree.scala +++ b/effekt/shared/src/main/scala/effekt/source/Tree.scala @@ -658,7 +658,14 @@ enum MatchPattern extends Tree { * * Currently should *only* occur in lambda-cases during Parsing */ - case MultiPattern(patterns: List[MatchPattern], span: Span) extends MatchPattern + case MultiPattern(patterns: List[MatchPattern], span: Span) + + /** + * A pattern with different alternatives that share the right and side + * + * case "a" | "b" => ... + */ + case OrPattern(patterns: List[MatchPattern], span: Span) } export MatchPattern.* diff --git a/effekt/shared/src/main/scala/effekt/typer/ExhaustivityChecker.scala b/effekt/shared/src/main/scala/effekt/typer/ExhaustivityChecker.scala index 8dde6c09e..264e67074 100644 --- a/effekt/shared/src/main/scala/effekt/typer/ExhaustivityChecker.scala +++ b/effekt/shared/src/main/scala/effekt/typer/ExhaustivityChecker.scala @@ -114,6 +114,7 @@ object ExhaustivityChecker { case LiteralPattern(lit, _) => Pattern.Literal(lit.value, lit.tpe) case MultiPattern(patterns, _) => Context.panic("Multi-pattern should have been split in preprocess already / nested MultiPattern") + case OrPattern(patterns, _) => Pattern.Any() // TODO } def preprocessGuard(g: source.MatchGuard)(using Context): Condition = g match { case MatchGuard.BooleanGuard(condition, _) =>