From 741e17e7e53487170d04d88bb231c433519cf3a1 Mon Sep 17 00:00:00 2001 From: Olivier Blanvillain Date: Wed, 28 Apr 2021 09:03:59 +0200 Subject: [PATCH 1/7] Fix #12260: Add underscore to match type syntax --- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 11 +++++++++-- docs/docs/internals/syntax.md | 2 +- tests/pos/unify-wildcard-patterns.scala | 13 +++++++++++++ 3 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 tests/pos/unify-wildcard-patterns.scala diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 63b6e9f8ac51..bd789534700a 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -2610,12 +2610,19 @@ object Parsers { }) } - /** TypeCaseClause ::= ‘case’ InfixType ‘=>’ Type [semi] + /** TypeCaseClause ::= ‘case’ (InfixType | ‘_’) ‘=>’ Type [semi] */ def typeCaseClause(): CaseDef = atSpan(in.offset) { val pat = inSepRegion(InCase) { accept(CASE) - infixType() + in.token match { + case USCORE if in.lookahead.isArrow => + val start = in.skipToken() + typeBounds().withSpan(Span(start, in.lastOffset, start)) + + case _ => + infixType() + } } CaseDef(pat, EmptyTree, atSpan(accept(ARROW)) { val t = typ() diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index 5cb03d5ab322..6ec1cbf6445f 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -289,7 +289,7 @@ CaseClauses ::= CaseClause { CaseClause } CaseClause ::= ‘case’ Pattern [Guard] ‘=>’ Block CaseDef(pat, guard?, block) // block starts at => ExprCaseClause ::= ‘case’ Pattern [Guard] ‘=>’ Expr TypeCaseClauses ::= TypeCaseClause { TypeCaseClause } -TypeCaseClause ::= ‘case’ InfixType ‘=>’ Type [semi] +TypeCaseClause ::= ‘case’ (InfixType | ‘_’) ‘=>’ Type [semi] Pattern ::= Pattern1 { ‘|’ Pattern1 } Alternative(pats) Pattern1 ::= Pattern2 [‘:’ RefinedType] Bind(name, Typed(Ident(wildcard), tpe)) diff --git a/tests/pos/unify-wildcard-patterns.scala b/tests/pos/unify-wildcard-patterns.scala new file mode 100644 index 000000000000..af88f4c80067 --- /dev/null +++ b/tests/pos/unify-wildcard-patterns.scala @@ -0,0 +1,13 @@ +// `case _ => expr` in a match expression should be equivalant to +// `case _: Any => expr`. Likewise, in a match type, `case _ => T` +// should be equivalant to `case Any => T`. + +object Test0 { + type M[X] = X match { case String => Int case Any => String } + def m[X](x: X): M[X] = x match { case _: String => 1 case _: Any => "s" } +} + +object Test2 { + type M[X] = X match { case String => Int case _ => String } + def m[X](x: X): M[X] = x match { case _: String => 1 case _: Any => "s" } +} From 0ef40265c562f3903b5362cc852e3db1d6dd9792 Mon Sep 17 00:00:00 2001 From: Olivier Blanvillain Date: Wed, 22 Sep 2021 11:25:06 +0200 Subject: [PATCH 2/7] Fix #9171: treat _ as _:Any in isMatchTypeShaped --- compiler/src/dotty/tools/dotc/core/Types.scala | 8 ++++++++ compiler/src/dotty/tools/dotc/typer/Typer.scala | 8 ++++++++ tests/pos/unify-wildcard-patterns.scala | 10 ++++++++++ 3 files changed, 26 insertions(+) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index b384cbdcb084..9521fedf5361 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4755,6 +4755,14 @@ object Types { object MatchType { def apply(bound: Type, scrutinee: Type, cases: List[Type])(using Context): MatchType = unique(new CachedMatchType(bound, scrutinee, cases)) + + /** Extractor for `case _ =>` match type patterns */ + object WildcardPattern { + def unapply(tp: Type)(using Context): Option[Type] = tp match { + case HKTypeLambda(LambdaParam(tl1, 0) :: Nil, defn.MatchCase(TypeParamRef(tl2, 0), bodyTp)) => Some(bodyTp) + case _ => None + } + } } // ------ ClassInfo, Type Bounds -------------------------------------------------- diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 6177faf6b6b0..77f1293a7a9b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1546,6 +1546,13 @@ class Typer extends Namer val Typed(_, tpt) = tpd.unbind(tpd.unsplice(pat1)) instantiateMatchTypeProto(pat1, pt) match { case defn.MatchCase(patternTp, _) => tpt.tpe frozen_=:= patternTp + case MatchType.WildcardPattern(_) => tpt.tpe frozen_=:= defn.AnyType + case _ => false + } + case (id @ Ident(nme.WILDCARD), pt) => + pt match { + case defn.MatchCase(patternTp, _) => defn.AnyType frozen_=:= patternTp + case MatchType.WildcardPattern(_) => true case _ => false } case _ => false @@ -1657,6 +1664,7 @@ class Typer extends Namer def caseRest(pat: Tree)(using Context) = { val pt1 = instantiateMatchTypeProto(pat, pt) match { case defn.MatchCase(_, bodyPt) => bodyPt + case MatchType.WildcardPattern(bodyPt) => bodyPt case pt => pt } val pat1 = indexPattern(tree).transform(pat) diff --git a/tests/pos/unify-wildcard-patterns.scala b/tests/pos/unify-wildcard-patterns.scala index af88f4c80067..9f18cebae1f5 100644 --- a/tests/pos/unify-wildcard-patterns.scala +++ b/tests/pos/unify-wildcard-patterns.scala @@ -7,7 +7,17 @@ object Test0 { def m[X](x: X): M[X] = x match { case _: String => 1 case _: Any => "s" } } +object Test1 { + type M[X] = X match { case String => Int case Any => String } + def m[X](x: X): M[X] = x match { case _: String => 1 case _ => "s" } +} + object Test2 { type M[X] = X match { case String => Int case _ => String } def m[X](x: X): M[X] = x match { case _: String => 1 case _: Any => "s" } } + +object Test3 { + type M[X] = X match { case String => Int case _ => String } + def m[X](x: X): M[X] = x match { case _: String => 1 case _ => "s" } +} From 82d9b5fd6f87c804fc22e464ac1f7ed61fee4dd3 Mon Sep 17 00:00:00 2001 From: Olivier Blanvillain Date: Thu, 27 May 2021 14:55:45 +0200 Subject: [PATCH 3/7] Parse 'case _' as 'Ident(_)' --- compiler/src/dotty/tools/dotc/core/Types.scala | 8 -------- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 3 +-- compiler/src/dotty/tools/dotc/typer/Typer.scala | 5 ++--- 3 files changed, 3 insertions(+), 13 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 9521fedf5361..b384cbdcb084 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4755,14 +4755,6 @@ object Types { object MatchType { def apply(bound: Type, scrutinee: Type, cases: List[Type])(using Context): MatchType = unique(new CachedMatchType(bound, scrutinee, cases)) - - /** Extractor for `case _ =>` match type patterns */ - object WildcardPattern { - def unapply(tp: Type)(using Context): Option[Type] = tp match { - case HKTypeLambda(LambdaParam(tl1, 0) :: Nil, defn.MatchCase(TypeParamRef(tl2, 0), bodyTp)) => Some(bodyTp) - case _ => None - } - } } // ------ ClassInfo, Type Bounds -------------------------------------------------- diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index bd789534700a..74381e969f43 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -2618,8 +2618,7 @@ object Parsers { in.token match { case USCORE if in.lookahead.isArrow => val start = in.skipToken() - typeBounds().withSpan(Span(start, in.lastOffset, start)) - + Ident(tpnme.WILDCARD).withSpan(Span(start, in.lastOffset, start)) case _ => infixType() } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 77f1293a7a9b..39b7fc1a8668 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -485,6 +485,8 @@ class Typer extends Namer if ctx.mode.is(Mode.Pattern) then if name == nme.WILDCARD then return tree.withType(pt) + if name == tpnme.WILDCARD then + return tree.withType(defn.AnyType) if untpd.isVarPattern(tree) && name.isTermName then return typed(desugar.patternVar(tree), pt) else if ctx.mode.is(Mode.QuotedPattern) then @@ -1546,13 +1548,11 @@ class Typer extends Namer val Typed(_, tpt) = tpd.unbind(tpd.unsplice(pat1)) instantiateMatchTypeProto(pat1, pt) match { case defn.MatchCase(patternTp, _) => tpt.tpe frozen_=:= patternTp - case MatchType.WildcardPattern(_) => tpt.tpe frozen_=:= defn.AnyType case _ => false } case (id @ Ident(nme.WILDCARD), pt) => pt match { case defn.MatchCase(patternTp, _) => defn.AnyType frozen_=:= patternTp - case MatchType.WildcardPattern(_) => true case _ => false } case _ => false @@ -1664,7 +1664,6 @@ class Typer extends Namer def caseRest(pat: Tree)(using Context) = { val pt1 = instantiateMatchTypeProto(pat, pt) match { case defn.MatchCase(_, bodyPt) => bodyPt - case MatchType.WildcardPattern(bodyPt) => bodyPt case pt => pt } val pat1 = indexPattern(tree).transform(pat) From 93d8e51c99338c3e71ba99036a8ad47fd9ed05b9 Mon Sep 17 00:00:00 2001 From: Olivier Blanvillain Date: Tue, 3 Aug 2021 13:40:16 +0200 Subject: [PATCH 4/7] Printing hack for `case _ =>` This is a hack to tell `case _ =>` and `case Any =>` apart while printing match type error messages. At this point in the compilation pipeline both `pat` types =:= Any, however because of the slight difference in representation we are able to restore the original syntax. Here is what `pat.toString` gives in each case: // case _ => (TypeRef(ThisType(TypeRef(NoPrefix,module class scala)),class Any)) // case Any => (TypeRef(TermRef(ThisType(TypeRef(NoPrefix,module class )),object scala),class Any)) The second case doesn't `eq defn.AnyType` because it starts from , and thus doesn't match the pattern added in this commit. --- compiler/src/dotty/tools/dotc/core/MatchTypeTrace.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/src/dotty/tools/dotc/core/MatchTypeTrace.scala b/compiler/src/dotty/tools/dotc/core/MatchTypeTrace.scala index 64a91dfb17e6..bc08dbc36eea 100644 --- a/compiler/src/dotty/tools/dotc/core/MatchTypeTrace.scala +++ b/compiler/src/dotty/tools/dotc/core/MatchTypeTrace.scala @@ -84,6 +84,7 @@ object MatchTypeTrace: private def caseText(tp: Type)(using Context): String = tp match case tp: HKTypeLambda => caseText(tp.resultType) + case defn.MatchCase(any, body) if any eq defn.AnyType => i"case _ => $body" case defn.MatchCase(pat, body) => i"case $pat => $body" case _ => i"case $tp" From daf4d412c01e60269a349daf53ee89613c44733b Mon Sep 17 00:00:00 2001 From: Olivier Blanvillain Date: Fri, 30 Jul 2021 22:38:30 +0200 Subject: [PATCH 5/7] Fix #13223: Disalow ? in match types pats/bodies --- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 4 ++-- tests/neg/12261.scala | 11 +++++++++++ tests/pos/9239.scala | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 tests/neg/12261.scala diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 74381e969f43..9137f883bed9 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -2620,11 +2620,11 @@ object Parsers { val start = in.skipToken() Ident(tpnme.WILDCARD).withSpan(Span(start, in.lastOffset, start)) case _ => - infixType() + rejectWildcardType(infixType()) } } CaseDef(pat, EmptyTree, atSpan(accept(ARROW)) { - val t = typ() + val t = rejectWildcardType(typ()) if in.token == SEMI then in.nextToken() newLinesOptWhenFollowedBy(CASE) t diff --git a/tests/neg/12261.scala b/tests/neg/12261.scala new file mode 100644 index 000000000000..9848e68fa160 --- /dev/null +++ b/tests/neg/12261.scala @@ -0,0 +1,11 @@ +type M0[X] = X match { + case ? => String // error: Unbound wildcard type +} + +type M1[X] = X match { + case Any => _ // error: Unbound wildcard type +} + +type M2[X] = X match { + case Any => ? // error: Unbound wildcard type +} diff --git a/tests/pos/9239.scala b/tests/pos/9239.scala index 8f3ddfbd1ced..c9c0ec268218 100644 --- a/tests/pos/9239.scala +++ b/tests/pos/9239.scala @@ -24,5 +24,5 @@ object ABug: N match case Zero => One case One => One - case ? => ![--[N]] × (N) + case _ => ![--[N]] × (N) case ? :: ? => ![--[N]] × (N) From 645989687f0738239f83d921efb52cd9f0b1cc23 Mon Sep 17 00:00:00 2001 From: Olivier Blanvillain Date: Sat, 31 Jul 2021 08:49:11 +0200 Subject: [PATCH 6/7] Also disalow unbound wildcard type in pattern expr --- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 2 +- tests/neg/12261.scala | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 9137f883bed9..0063f6eb0af9 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1824,7 +1824,7 @@ object Parsers { def typeDependingOn(location: Location): Tree = if location.inParens then typ() - else if location.inPattern then refinedType() + else if location.inPattern then rejectWildcardType(refinedType()) else infixType() /* ----------- EXPRESSIONS ------------------------------------------------ */ diff --git a/tests/neg/12261.scala b/tests/neg/12261.scala index 9848e68fa160..70120671a05b 100644 --- a/tests/neg/12261.scala +++ b/tests/neg/12261.scala @@ -9,3 +9,7 @@ type M1[X] = X match { type M2[X] = X match { case Any => ? // error: Unbound wildcard type } + +val a = "" match { case _: _ => () } // error: Unbound wildcard type + +val b = try { } catch { case _: _ => () } // error: Unbound wildcard type From bb96d9524f1d996a6817e21033da7253daa02384 Mon Sep 17 00:00:00 2001 From: Olivier Blanvillain Date: Tue, 3 Aug 2021 11:46:09 +0200 Subject: [PATCH 7/7] Update community build (scodec) --- community-build/community-projects/scodec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/community-build/community-projects/scodec b/community-build/community-projects/scodec index 18047cac1feb..d59c0440b078 160000 --- a/community-build/community-projects/scodec +++ b/community-build/community-projects/scodec @@ -1 +1 @@ -Subproject commit 18047cac1febc840e03dee91a31183d64e632222 +Subproject commit d59c0440b078292dedff10b6743d6a086f468527