alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Scala example source code file (TypeDiagnostics.scala)

This example Scala source code file (TypeDiagnostics.scala) is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Java - Scala tags/keywords

list, list, nil, position, some, string, string, symbol, t, tree, tree, type, type, typediag

The Scala TypeDiagnostics.scala source code

/* NSC -- new Scala compiler
 * Copyright 2005-2011 LAMP/EPFL
 * @author  Paul Phillips
 */

package scala.tools.nsc
package typechecker

import scala.collection.mutable
import scala.collection.mutable.ListBuffer
import scala.util.control.ControlThrowable
import scala.util.control.Exception.ultimately
import symtab.Flags._
import PartialFunction._

/** An interface to enable higher configurability of diagnostic messages
 *  regarding type errors.  This is barely a beginning as error messages are
 *  distributed far and wide across the codebase.  The plan is to partition
 *  error messages into some broad groups and provide some mechanism for
 *  being more or less verbose on a selective basis.  Possible groups include
 *  such examples as
 * 
 *    arity errors
 *    kind errors
 *    variance errors
 *    ambiguity errors
 *    volatility/stability errors
 *    implementation restrictions
 *
 *  And more, and there is plenty of overlap, so it'll be a process.
 *
 *  @author Paul Phillips
 *  @version 1.0
 */
trait TypeDiagnostics {
  self: Analyzer =>

  import global._
  import definitions._
  import global.typer.infer
  
  private def currentUnit = currentRun.currentUnit
  
  /** It can be quite difficult to know which of the many functions called "error"
   *  is being called at any given point in the compiler.  To alleviate this I am
   *  renaming such functions inside this trait based on where it originated.
   */
  def inferError(pos: Position, msg: String) = infer.error(pos, msg)
  
  /** The common situation of making sure nothing is erroneous could be
   *  nicer if Symbols, Types, and Trees all implemented some common interface
   *  in which isErroneous and similar would be placed.
   */
  def noErroneousTypes(tps: Type*)    = tps forall (x => !x.isErroneous)
  def noErroneousSyms(syms: Symbol*)  = syms forall (x => !x.isErroneous)
  def noErroneousTrees(trees: Tree*)  = trees forall (x => !x.isErroneous)
  
  /** For errors which are artifacts of the implementation: such messages
   *  indicate that the restriction may be lifted in the future.
   */
  def restrictionWarning(pos: Position, unit: CompilationUnit, msg: String): Unit =
    unit.warning(pos, "Implementation restriction: " + msg)
  def restrictionError(pos: Position, unit: CompilationUnit, msg: String): Unit =
    unit.error(pos, "Implementation restriction: " + msg)
  
  /** A map of Positions to addendums - if an error involves a position in
   *  the map, the addendum should also be printed.
   */
  private var addendums = mutable.Map[Position, () => String]()

  def setAddendum(pos: Position, msg: () => String) =
    if (pos != NoPosition)
      addendums(pos) = msg

  def withAddendum(pos: Position) = (_: String) + addendums.getOrElse(pos, () => "")()

  def decodeWithKind(name: Name, owner: Symbol): String = {
    val prefix = (
      if (name.isTypeName) "type "
      else if (owner.isPackageClass) "object "
      else "value "
    )
    prefix + name.decode
  }
  
  /** Does the positioned line assigned to t1 precede that of t2?
   */
  def linePrecedes(t1: Tree, t2: Tree) = t1.pos.isDefined && t1.pos.isDefined && t1.pos.line < t2.pos.line
  
  def notAMember(sel: Tree, qual: Tree, name: Name) = {
    val owner            = qual.tpe.typeSymbol
    val target           = qual.tpe.widen
    def targetKindString = if (owner.isTypeParameterOrSkolem) "type parameter " else ""
    def nameString       = decodeWithKind(name, owner)
    /** Illuminating some common situations and errors a bit further. */
    def addendum         = {
      val companion = {
        if (name.isTermName && owner.isPackageClass) {
          target.member(name.toTypeName) match {
            case NoSymbol => ""
            case sym      => "\nNote: %s exists, but it has no companion object.".format(sym)
          }
        }
        else ""
      }
      val semicolon = (
        if (linePrecedes(qual, sel))
          "\npossible cause: maybe a semicolon is missing before `"+nameString+"'?"
        else
          ""
      )
      companion + semicolon
    }

    inferError(
      sel.pos,
      withAddendum(qual.pos)(
        if (name == nme.CONSTRUCTOR) target + " does not have a constructor"
        else nameString + " is not a member of " + targetKindString + target + addendum
      )
    )
  }
  
  /** Only prints the parameter names if they're not synthetic,
   *  since "x$1: Int" does not offer any more information than "Int".
   */
  private def methodTypeErrorString(tp: Type) = tp match {
    case mt @ MethodType(params, resultType)  =>
      def forString = 
        if (params exists (_.isSynthetic)) params map (_.tpe)
        else params map (_.defString)
                  
       forString.mkString("(", ",", ")") + resultType
    case x                                    => x.toString
  }

  def alternatives(tree: Tree): List[Type] = tree.tpe match {
    case OverloadedType(pre, alternatives)  => alternatives map pre.memberType
    case _                                  => Nil
  }
  def alternativesString(tree: Tree) =
    alternatives(tree) map (x => "  " + methodTypeErrorString(x)) mkString ("", " <and>\n", "\n")

  def missingParameterTypeMsg(fun: Tree, vparam: ValDef, pt: Type) = {
    def anonMessage = (
      "\nThe argument types of an anonymous function must be fully known. (SLS 8.5)" +
      "\nExpected type was: " + pt.toLongString
    )
    val suffix =
      if (!vparam.mods.isSynthetic) ""
      else " for expanded function" + (fun match {
        case Function(_, Match(_, _)) => anonMessage
        case _                        => " " + fun
      })

    "missing parameter type" + suffix
  }
  
  def treeSymTypeMsg(tree: Tree): String = {
    val sym               = tree.symbol
    def hasParams         = tree.tpe.paramSectionCount > 0    
    def preResultString   = if (hasParams) ": " else " of type "

    def nullMessage       = "expression of type " + tree.tpe
    def overloadedMessage = "overloaded method " + sym + " with alternatives:\n" + alternativesString(tree)
    def moduleMessage     = "" + sym
    def defaultMessage    = moduleMessage + preResultString + tree.tpe
    def applyMessage      = defaultMessage + tree.symbol.locationString

    if (sym == null) nullMessage
    else if (sym.isOverloaded) overloadedMessage
    else if (sym.isModule) moduleMessage
    else if (sym.name == nme.apply) applyMessage
    else defaultMessage
  }
  
  def notEnoughArgumentsMsg(fun: Tree, missing: List[Symbol]): String = {
    val suffix = {
      if (missing.isEmpty) ""
      else {
        val keep = missing take 3 map (_.name)
        ".\nUnspecified value parameter%s %s".format(
          if (missing.tail.isEmpty) "" else "s",
          if (missing drop 3 nonEmpty) (keep :+ "...").mkString(", ")
          else keep.mkString("", ", ", ".")
        )
      }
    }

    "not enough arguments for " + treeSymTypeMsg(fun) + suffix
  }
  
  def applyErrorMsg(tree: Tree, msg: String, argtpes: List[Type], pt: Type) = {
    def asParams(xs: List[Any]) = xs.mkString("(", ", ", ")")

    def resType   = if (pt isWildcard) "" else " with expected result type " + pt
    def allTypes  = (alternatives(tree) flatMap (_.paramTypes)) ++ argtpes :+ pt
    def locals    = alternatives(tree) flatMap (_.typeParams)

    withDisambiguation(locals, allTypes: _*) {
      treeSymTypeMsg(tree) + msg + asParams(argtpes) + resType
    }
  }
  
  def disambiguate(ss: List[String]) = ss match {
    case Nil      => Nil
    case s :: ss  => s :: (ss map { case `s` => "(some other)"+s ; case x => x })
  }
  
  // todo: use also for other error messages
  def existentialContext(tp: Type) = tp.existentialSkolems match {
    case Nil  => ""
    case xs   => " where " + (disambiguate(xs map (_.existentialToString)) mkString ", ")
  }
  
  def varianceWord(sym: Symbol): String =
    if (sym.variance == 1) "covariant"
    else if (sym.variance == -1) "contravariant"
    else "invariant"
  
  /** Look through the base types of the found type for any which
   *  might have been valid subtypes if given conformant type arguments.
   *  Examine those for situations where the type error would have been
   *  eliminated if the variance were different.  In such cases, append
   *  an additional explanatory message.
   *
   *  TODO: handle type aliases better.
   */
  def explainVariance(found: Type, req: Type): String = {
    found.baseTypeSeq.toList foreach { tp =>
      if (tp.typeSymbol isSubClass req.typeSymbol) {
        val foundArgs = tp.typeArgs
        val reqArgs   = req.typeArgs
        val params    = req.typeConstructor.typeParams
        
        if (foundArgs.nonEmpty && foundArgs.length == reqArgs.length) {
          val relationships = (foundArgs, reqArgs, params).zipped map {
            case (arg, reqArg, param) =>      
              def mkMsg(isSubtype: Boolean) = {
                val op      = if (isSubtype) "<:" else ">:"
                val suggest = if (isSubtype) "+" else "-"
                val reqsym  = req.typeSymbol
                def isJava  = reqsym.isJavaDefined
                def isScala = reqsym hasTransOwner ScalaPackageClass
                
                val explainFound = "%s %s %s%s, but ".format(
                  arg, op, reqArg,
                  // If the message involves a type from the base type sequence rather than the
                  // actual found type, we need to explain why we're talking about it.  Less brute
                  // force measures than comparing normalized Strings were producing error messages
                  // like "and java.util.ArrayList[String] <: java.util.ArrayList[String]" but there
                  // should be a cleaner way to do this.
                  if (found.normalize.toString == tp.normalize.toString) ""
                  else " (and %s <: %s)".format(found, tp)
                )
                val explainDef = {
                  val prepend = if (isJava) "Java-defined " else ""
                  "%s%s is %s in %s.".format(prepend, reqsym, varianceWord(param), param)
                }
                // Don't suggest they change the class declaration if it's somewhere
                // under scala.* or defined in a java class, because attempting either
                // would be fruitless.
                val suggestChange = "\nYou may wish to " + (
                  if (isScala || isJava)
                    "investigate a wildcard type such as `_ %s %s`. (SLS 3.2.10)".format(op, reqArg)
                  else
                    "define %s as %s%s instead. (SLS 4.5)".format(param.name, suggest, param.name)
                )

                Some("Note: " + explainFound + explainDef + suggestChange)
              }
              // In these cases the arg is OK and needs no explanation.
              val conforms = (
                   (arg =:= reqArg)
                || ((arg <:< reqArg) && param.isCovariant)
                || ((reqArg <:< arg) && param.isContravariant)
              )
              val invariant = param.variance == 0
              
              if (conforms)                             Some("")
              else if ((arg <:< reqArg) && invariant)   mkMsg(true)   // covariant relationship
              else if ((reqArg <:< arg) && invariant)   mkMsg(false)  // contravariant relationship
              else None // we assume in other cases our ham-fisted advice will merely serve to confuse
          }
          val messages = relationships.flatten
          // the condition verifies no type argument came back None
          if (messages.size == foundArgs.size)
            return messages filterNot (_ == "") mkString ("\n", "\n", "")
        }
      }
    }
    ""    // no elaborable variance situation found
  }
  
  def foundReqMsg(found: Type, req: Type): String = {
    (withDisambiguation(List(), found, req) {
      ";\n found   : " + found.toLongString + existentialContext(found) +
       "\n required: " + req + existentialContext(req)
    }) + explainVariance(found, req)
  }
  
  case class TypeDiag(tp: Type, sym: Symbol) extends Ordered[TypeDiag] {
    // save the name because it will be mutated until it has been
    // distinguished from the other types in the same error message
    private val savedName = sym.name
    def restoreName()     = sym.name = savedName
    def isAltered         = sym.name != savedName
    def modifyName(f: String => String) =
      sym.name = newTypeName(f(sym.name.toString))

    // functions to manipulate the name
    def preQualify()   = modifyName(trueOwner.fullName + "." + _)
    def postQualify()  = modifyName(_ + "(in " + trueOwner + ")")
    def scalaQualify() = if (inPredefOrScala) preQualify()
    def typeQualify()  = if (sym.isTypeParameterOrSkolem) postQualify()
    def nameQualify()  = if (trueOwner.isPackageClass) preQualify() else postQualify()

    def trueOwner  = tp.typeSymbol.owner.skipPackageObject
    def aliasOwner = tp.typeSymbolDirect.owner.skipPackageObject
    def owners     = List(trueOwner, aliasOwner)

    private def scalaAndPredef = Set(ScalaPackageClass, PredefModuleClass)
    def inPredefOrScala = owners exists scalaAndPredef
    
    def sym_==(other: TypeDiag)     = tp.typeSymbol == other.tp.typeSymbol
    def owner_==(other: TypeDiag)   = trueOwner == other.trueOwner
    def string_==(other: TypeDiag)  = tp.toString == other.tp.toString
    def name_==(other: TypeDiag)    = sym.name == other.sym.name

    def compare(other: TypeDiag) = 
      if (this == other) 0
      else if (sym isLess other.sym) -1
      else 1

    override def toString = {
      """
      |tp = %s
      |tp.typeSymbol = %s
      |tp.typeSymbol.owner = %s
      |tp.typeSymbolDirect = %s
      |tp.typeSymbolDirect.owner = %s
      |inPredefOrScala = %s
      """.stripMargin.format(
        tp, tp.typeSymbol, tp.typeSymbol.owner, tp.typeSymbolDirect, tp.typeSymbolDirect.owner, inPredefOrScala
      )
    }
  }
  private def typeDiags(locals: List[Symbol], types: Type*): List[TypeDiag] = {
    object SymExtractor {
      def unapply(x: Any) = x match {
        case t @ ConstantType(_)    => Some(t -> t.underlying.typeSymbol)
        case t @ TypeRef(_, sym, _) => if (locals contains sym) None else Some(t -> sym)
        case _                      => None
      }
    }
    
    for (tp <- types.toList; SymExtractor(t, sym) <- tp) yield TypeDiag(t, sym)
  }
  
  /** The distinct pairs from an ordered list. */
  private def pairs[T <: Ordered[T]](xs: Seq[T]): Seq[(T, T)] = {
    for (el1 <- xs ; el2 <- xs ; if el1 < el2) yield
      ((el1, el2))
  }

  /** Given any number of types, alters the name information in the symbols
   *  until they can be distinguished from one another: then executes the given
   *  code.  The names are restored and the result is returned.
   */
  def withDisambiguation[T](locals: List[Symbol], types: Type*)(op: => T): T = {
    val typeRefs = typeDiags(locals, types: _*)
    val toCheck  = pairs(typeRefs) filterNot { case (td1, td2) => td1 sym_== td2 }

    ultimately(typeRefs foreach (_.restoreName())) {
      for ((td1, td2) <- toCheck) {
        val tds = List(td1, td2)

        // If the types print identically, qualify them:
        //   a) If the dealiased owner is a package, the full path
        //   b) Otherwise, append (in <owner>)
        if (td1 string_== td2)
          tds foreach (_.nameQualify())

        // If they have the same simple name, and either of them is in the
        // scala package or predef, qualify with scala so it is not confusing why
        // e.g. java.util.Iterator and Iterator are different types.
        if (td1 name_== td2)
          tds foreach (_.scalaQualify())

        // If they still print identically:
        //   a) If they are type parameters with different owners, append (in <owner>)
        //   b) Failing that, the best we can do is append "(some other)" to the latter.
        if (td1 string_== td2) {
          if (td1 owner_== td2)
            td2.modifyName("(some other)" + _)
          else
            tds foreach (_.typeQualify())
        }
      }
      // performing the actual operation
      op
    }
  }
  
  trait TyperDiagnostics {
    self: Typer =>

    private def contextError(pos: Position, msg: String) = context.error(pos, msg)
    private def contextError(pos: Position, err: Throwable) = context.error(pos, err)
    
    object checkDead {
      private var expr: Symbol = NoSymbol
      private def exprOK = expr != Object_synchronized
      private def treeOK(tree: Tree) = tree.tpe != null && tree.tpe.typeSymbol == NothingClass

      def updateExpr(fn: Tree) = {
        if (fn.symbol != null && fn.symbol.isMethod && !fn.symbol.isConstructor)
          checkDead.expr = fn.symbol
      }
      def apply(tree: Tree): Tree = {
        // Error suppression will squash some of these warnings unless we circumvent it.
        // It is presumed if you are using a -Y option you would really like to hear
        // the warnings you've requested.
        if (settings.warnDeadCode.value && context.unit != null && treeOK(tree) && exprOK) {
          val saved = context.reportGeneralErrors
          try {
            context.reportGeneralErrors = true
            context.warning(tree.pos, "dead code following this construct")
          }
          finally context.reportGeneralErrors = saved
        }
        tree
      }

      // The checkDead call from typedArg is more selective.
      def inMode(mode: Int, tree: Tree): Tree = {
        val modeOK = (mode & (EXPRmode | BYVALmode | POLYmode)) == (EXPRmode | BYVALmode)
        if (modeOK) apply(tree)
        else tree
      }
    }
    
    def symWasOverloaded(sym: Symbol) = sym.owner.isClass && sym.owner.info.member(sym.name).isOverloaded
    def cyclicAdjective(sym: Symbol)  = if (symWasOverloaded(sym)) "overloaded" else "recursive"
    
    /** Returns Some(msg) if the given tree is untyped apparently due
     *  to a cyclic reference, and None otherwise.
     */
    def cyclicReferenceMessage(sym: Symbol, tree: Tree) = condOpt(tree) {      
      case ValDef(_, _, tpt, _) if tpt.tpe == null        => "recursive "+sym+" needs type"
      case DefDef(_, _, _, _, tpt, _) if tpt.tpe == null  => List(cyclicAdjective(sym), sym, "needs result type") mkString " "
    }
  
    /** Report a type error.
     *
     *  @param pos0   The position where to report the error
     *  @param ex     The exception that caused the error
     */
    def reportTypeError(pos: Position, ex: TypeError) {
      if (ex.pos == NoPosition) ex.pos = pos
      if (!context.reportGeneralErrors) throw ex
      if (settings.debug.value) ex.printStackTrace()

      ex match {
        case CyclicReference(sym, info: TypeCompleter) =>
          contextError(ex.pos, cyclicReferenceMessage(sym, info.tree) getOrElse ex.getMessage())
          
          if (sym == ObjectClass) 
            throw new FatalError("cannot redefine root "+sym)
        case _ =>
          contextError(ex.pos, ex)
      }
    }
  }
}

Other Scala examples (source code examples)

Here is a short list of links related to this Scala TypeDiagnostics.scala source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.