2016-05-03 1 views
1

J'ai essayé de générer une compilation SBT pour un projet open source que je voudrais référencer dans mon projet, et je suis tombé sur ce qui semblait être un bug de compilateur.Solution de contournement pour un bogue scala.language.dynamics dans le compilateur scala 2.10

Le code suivant compile, et fonctionne comme prévu en ide scala eclipse /, mais le compilateur scala 2.10.6 est incapable de digérer:

package foo 

import scala.language.dynamics 

object Caller extends App { 
    val client = new Client() // initialise an R interpreter 
    client.x = 1.0 
} 
class Client extends Dynamic { 
    var map = Map.empty[String, Any] 
    def selectDynamic(name: String) = map get name getOrElse sys.error("field not found") 
    def updateDynamic(name: String)(value: Any) { map += name -> value } 
} 

Voici mon build.sbt:

scalaVersion := "2.10.6" 

libraryDependencies++= Seq(
    "org.scalanlp" %% "breeze" % "0.12" 
) 

Quand je précise scalaVersion: = 2.10.6, je reçois l'erreur de compilation suivante:

[error] /home/philwalk/dynsbt/src/main/scala/foo/Caller.scala:8: type mismatch; 
[error] found : foo.Caller.client.type (with underlying type foo.Client) 
[error] required: ?{def x: ?} 
[error] Note that implicit conversions are not applicable because they are ambiguous: 
[error] both method any2Ensuring in object Predef of type [A](x: A)Ensuring[A] 
[error] and method any2ArrowAssoc in object Predef of type [A](x: A)ArrowAssoc[A] 
[error] are possible conversion functions from foo.Caller.client.type to ?{def x: ?} 
[error] client.x = Seq("a","b","c") 
[error] ^
[error] one error found 
[error] (compile:compileIncremental) Compilation failed 
[error] Total time: 3 s, completed May 3, 2016 11:03:08 AM 

Avec s calaVersion: = 2.11.8, pas de problème, bien que j'ai besoin de cross-compiler, donc ce n'est pas une solution de rechange.

Un autre indice est que je peux cacher le problème en changeant cette ligne de code:

client.x = 1.0 

à ceci:

client.xx = 1.0 

Je vois aussi le problème quand je compile directement avec scalac 2.10. 6. Pour contourner le problème, je pourrais refactoriser le projet afin d'utiliser des noms de champs plus longs qu'un seul caractère, bien que ce ne soit pas mon projet, je suis quelque peu limité sur ce que je peux accepter comme solution de contournement. En outre, il s'agit d'un projet breeze.linalg et il serait très difficile de ne pas autoriser les noms de matrice et de vecteur à caractère unique.

Il a fallu quelques heures pour réduire le problème à ce fragment de code d'un projet plus important, et je préférerais ne pas imposer de limitations sur la version scala 2.10 de cette bibliothèque open source. Puisque ce bug semble avoir été corrigé dans la version 2.11, je suppose qu'il a été décidé de ne pas rétrograder le correctif à 2.10.

J'ai changé le titre pour refléter l'existence d'une solution de contournement (nom de champ plus long).

+0

Avez-vous l'erreur si vous exécutez propre et ensuite compiler? – Martin

+0

oui, c'est assez stable et répétable. Aussi facile à dupliquer, il suffit de coller le code source ci-dessus dans un fichier situé dans src/main/scala/foo/Client.scala, puis d'essayer de compiler avec sbt 0.13.11 (il sera par défaut à scala 2.10). 6, comme cela arrive). – philwalk

+1

J'ai essayé d'exécuter directement scalac 2.10.6, et j'ai la même erreur, donc je suppose que ce n'est pas la faute de sbt. Dans sbt, lancez 'compile', puis' last' pour obtenir les paramètres exacts donnés à scalac. Edit: Même dans la variable REPL, vous n'avez pas besoin de la dépendance de la bibliothèque btw. – Martin

Répondre

2

C'est la faute de Scala 2.10, pas de sbt.

Le problème est que, dans Predef, dont le contenu est importé dans chaque fichier Scala, il y a deux classes problématiques: Ensuring et ArrowAssoc. Les membres de ces deux classes sont disponibles via des conversions implicites sur n'importe quel type de valeur. ArrowAssoc, par exemple, est la raison pour laquelle vous pouvez faire 1 -> 2 pour construire un tuple (1, 2).

Maintenant, ces classes avaient, en 2.10, la propriété très malheureuse de déclarer un membre nommé x! Même si obsolète en 2.10, il existe un problème sérieux pour les usages de scala.Dynamic.

Dans votre code, client.x = 1.0 teste d'abord si client.x existe en tant que val/getter sur le type client. Il n'est pas, vraiment, mais serait disponible si nous avons utilisé les conversions implicites dans Predef pour le convertir en Ensuring ou ArrowAssoc.Puisque les conversions implicites ont une priorité plus élevée que le traitement selectDynamic, le compilateur Scala essaie de les utiliser. Mais comme il y a deux conversions tout aussi valides, elles sont ambiguës, et vous obtenez une erreur de compilation.

En résumé, cela est une conséquence malheureuse de 2 faits:

  • Il y a des conversions implicites fournissant un membre x sur tout
  • Cette conversion a priorité sur le traitement Dynamic.

La façon de résoudre ce problème, dans votre exemple, est de déclarer explicitement x dans Client, en tant que porteur de selectDynamic et updateDynamic:

class Client extends Dynamic { 
    var map = Map.empty[String, Any] 
    def selectDynamic(name: String) = map get name getOrElse sys.error("field not found") 
    def updateDynamic(name: String)(value: Any) { map += name -> value } 

    // Work around the annoying implicits in Predef in Scala 2.10. 
    def x: Any = selectDynamic("x") 
    def x_=(value: Any): Unit = updateDynamic("x")(value) 
} 

Maintenant, client.x sera bien sûr utiliser l'a déclaré explicitement x et son setter x_= dans Client, qui va déléguer à selectDynamic/updateDynamic.

+0

Merci, il semble que je peux avoir mon gâteau et le manger aussi :) – philwalk