Pytanie Dlaczego `Some (123) .isInstanceOf [Option [List [String]]]]` * not * daje niezaznaczone ostrzeżenie?


Podczas używania .isInstanceOf[GenericType[SomeOtherType]], gdzie GenericType i SomeOtherType są arbitralne typy (odpowiedniego rodzaju), kompilator Scala daje niezaznaczone ostrzeżenie z powodu usunięcia typu:

scala> Some(123).isInstanceOf[Option[Int]]
<console>:8: warning: non variable type-argument Int in type Option[Int] is unchecked since it is eliminated by erasure
              Some(123).isInstanceOf[Option[Int]]
                                    ^
res0: Boolean = true

scala> Some(123).isInstanceOf[Option[String]]
<console>:8: warning: non variable type-argument String in type Option[String] is unchecked since it is eliminated by erasure
              Some(123).isInstanceOf[Option[String]]
                                    ^
res1: Boolean = true

Jeśli jednak SomeOtherType sam jest typem ogólnym (np. List[String]), nie jest emitowane ostrzeżenie:

scala> Some(123).isInstanceOf[Option[List[String]]]
res2: Boolean = true

scala> Some(123).isInstanceOf[Option[Option[Int]]]
res3: Boolean = true

scala> Some(123).isInstanceOf[Option[List[Int => String]]]
res4: Boolean = true

scala> Some(123).isInstanceOf[Option[(String, Double)]]
res5: Boolean = true

scala> Some(123).isInstanceOf[Option[String => Double]]
res6: Boolean = true

(przypomnij, że krotki i => są syntaktycznym cukrem dla Tuple2[] i Function2[] typy ogólne)

Dlaczego nie jest emitowane ostrzeżenie? (Wszystkie te są w Scala REPL 2.9.1, z -unchecked opcja.)


15
2017-07-19 05:14


pochodzenie




Odpowiedzi:


Spojrzałem na źródła kompilatora Scali i odkryłem coś interesującego

scala.tools.nsc.typechecker.Infer

w którym znajduje się ostrzeżenie. Jeśli przyjrzysz się uważnie linii 1399 do:

def checkCheckable(pos: Position, tp: Type, kind: String)

w którym generowane jest ostrzeżenie, widzisz niektóre metody zagnieżdżone, w tym metodę sprawdzania:

 def check(tp: Type, bound: List[Symbol]) {
        def isLocalBinding(sym: Symbol) =
          sym.isAbstractType &&
          ((bound contains sym) ||
           sym.name == tpnme.WILDCARD || {
            val e = context.scope.lookupEntry(sym.name)
            (e ne null) && e.sym == sym && !e.sym.isTypeParameterOrSkolem && e.owner == context.scope
          })
        tp match {
          case SingleType(pre, _) =>
            check(pre, bound)
          case TypeRef(pre, sym, args) =>
            if (sym.isAbstractType) {
              if (!isLocalBinding(sym)) patternWarning(tp, "abstract type ")
            } else if (sym.isAliasType) {
              check(tp.normalize, bound)
            } else if (sym == NothingClass || sym == NullClass || sym == AnyValClass) {
              error(pos, "type "+tp+" cannot be used in a type pattern or isInstanceOf test")
            } else {
              for (arg <- args) {
                if (sym == ArrayClass) check(arg, bound)
                else if (arg.typeArgs.nonEmpty) ()   // avoid spurious warnings with higher-kinded types
                else arg match {
                  case TypeRef(_, sym, _) if isLocalBinding(sym) =>
                    ;
                  case _ =>
                    patternWarning(arg, "non variable type-argument ")
                }
              }
            }
            check(pre, bound)
          case RefinedType(parents, decls) =>
            if (decls.isEmpty) for (p <- parents) check(p, bound)
            else patternWarning(tp, "refinement ")
          case ExistentialType(quantified, tp1) =>
            check(tp1, bound ::: quantified)
          case ThisType(_) =>
            ;
          case NoPrefix =>
            ;
          case _ =>
            patternWarning(tp, "type ")
        }
  }

Chociaż nie jestem ekspertem od kompilatora Scala, powinniśmy wszyscy podziękować tym facetom za uczynienie kodu tak oczywistym. Zajrzyjmy do wnętrza tp match blok i przypadki leczone:

  • Jeśli jest to pojedynczy typ
  • Jeśli jest to ref
    • Jeśli jest to typ abstrakcyjny
    • Jeśli jest to typ aliasu
    • Jeśli ma wartość Null, Nic lub Anyval
    • Wszystkie inne przypadki

Jeśli spojrzysz na wszystkie inne przypadki, pojawi się wiersz, który również zostanie skomentowany:

else if (arg.typeArgs.nonEmpty) ()   // avoid spurious warnings with higher-kinded types

To mówi dokładnie, co się stanie, jeśli twój typ ma inny parametr typu (jak Function2 lub Tuple2). Funkcja sprawdzania zwraca jednostkę bez przeprowadzania jakiegokolwiek testu.

Nie wiem z jakiego powodu zostało to zrobione w ten sposób, ale możesz chcieć otworzyć błąd na https://issues.scala-lang.org/browse/SI podając kod, który tutaj zamieściłeś, jako doskonały przypadek testowy i odwołanie do źródła Infer.scala, które skopiowałem powyżej.


19
2017-07-19 07:11



Wielkie śledztwo! - Jean-Philippe Pellet
Rzeczywiście, dobra robota! - pedrofurla
+1 do cytowania kompilatora. :-) - Daniel C. Sobral
+1 za rozwianie moich romantycznych złudzeń, że kompilator został napisany w całości w formie Backus-Naur i kombinatorach parserów :( - Luigi Plinge
Dzięki za szczegółową analizę. Złożyłem błąd 6125. - Mechanical snail