Skip to content

Commit

Permalink
When checking for a non-empty type intersection don't treat Singleton…
Browse files Browse the repository at this point in the history
… as final

inferTypedPattern checks for a non-empty intersection between the static
type of the scrutinee and the inferred pattern type using
Type#isPopulated. The latter has a shortcut execution path for final
classes which accidentally picks up Singleton (it's a class and has the
FINAL flag set, so that's understandable). However all singleton types
are at least conceptually subtypes of Singleton so this leads to
incorrect behaviour in cases of the form,

  Tuple1[Foo.type](Foo) match {
    case Tuple1(foo: Singleton) => foo
  }

  error: pattern type is incompatible with expected type;
   found   : Singleton
   required: Test.Foo.type
      case Tuple1(foo: Singleton) => foo
		       ^
  one error found

This PR fixes that by adding a check that the shortcut is only followed
if the type isn't Singleton and also makes a similar change in
isEffectivelyFinal.

This turned out to be the cause of,

  typelevel#152

reported against the SIP-23 literal types extension, but as can be seen
the example here it's an issue for singleton types generally.
  • Loading branch information
milessabin committed Jan 12, 2018
1 parent c7de0eb commit 263829f
Show file tree
Hide file tree
Showing 3 changed files with 12 additions and 5 deletions.
2 changes: 1 addition & 1 deletion src/reflect/scala/reflect/internal/Symbols.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1017,7 +1017,7 @@ trait Symbols extends api.Symbols { self: SymbolTable =>

/** Is this symbol effectively final? I.e, it cannot be overridden */
final def isEffectivelyFinal: Boolean = (
(this hasFlag FINAL | PACKAGE)
((this hasFlag FINAL | PACKAGE) && this != SingletonClass)
|| isModuleOrModuleClass && (isTopLevel || !settings.overrideObjects)
|| isTerm && (isPrivate || isLocalToBlock || (hasAllFlags(notPRIVATE | METHOD) && !hasFlag(DEFERRED)))
|| isClass && originalOwner.isTerm && children.isEmpty // we track known subclasses of term-owned classes, use that infer finality
Expand Down
9 changes: 5 additions & 4 deletions src/reflect/scala/reflect/internal/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4133,12 +4133,13 @@ trait Types
throw new MatchError((tp1, tp2))
}

def check(tp1: Type, tp2: Type) = (
if (tp1.typeSymbol.isClass && tp1.typeSymbol.hasFlag(FINAL))
tp1 <:< tp2 || isNumericValueClass(tp1.typeSymbol) && isNumericValueClass(tp2.typeSymbol)
def check(tp1: Type, tp2: Type) = {
val sym1 = tp1.typeSymbol
if (sym1.isClass && sym1.hasFlag(FINAL) && sym1 != SingletonClass)
tp1 <:< tp2 || isNumericValueClass(sym1) && isNumericValueClass(tp2.typeSymbol)
else tp1.baseClasses forall (bc =>
tp2.baseTypeIndex(bc) < 0 || isConsistent(tp1.baseType(bc), tp2.baseType(bc)))
)
}

check(tp1, tp2) && check(tp2, tp1)
}
Expand Down
6 changes: 6 additions & 0 deletions test/files/pos/t10569.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
object Test {
object Foo
Tuple1[Foo.type](Foo) match {
case Tuple1(foo: Singleton) => foo
}
}

0 comments on commit 263829f

Please sign in to comment.