All Downloads are FREE. Search and download functionalities are using the official Maven repository.

airyfotr.linter_2.11.0.1.17.source-code.LinterPlugin.scala Maven / Gradle / Ivy

The newest version!
/**
 *   Copyright 2012 Foursquare Labs, Inc.
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */

package org.psywerx.hairyfotr

import scala.collection.mutable
import scala.tools.nsc.plugins.{ Plugin, PluginComponent }
import scala.tools.nsc.{ Global, Phase, Properties }

final class LinterPlugin(val global: Global) extends Plugin {
  import org.psywerx.hairyfotr.Utils._
  import global._

  val name = "linter"
  val description = "a static analysis compiler plugin to help protect against bugs and style problems"
  val components: List[PluginComponent] =
    if (settings.isInstanceOf[scala.tools.nsc.doc.Settings]) {
      inform("This is a scaladoc compilation, Linter is disabled.")
      List()
    } else {
      List(PreTyperComponent, PostTyperComponent, PostTyperInterpreterComponent, PostRefChecksComponent)
    }

  override val optionsHelp: Option[String] = Some(Seq(
    LinterOptions.DisableArgument+":plus+separated+warning+names",
    LinterOptions.EnableOnlyArgument+":plus+separated+warning+names",
    LinterOptions.PrintWarningNames+":true|false"
  ).map(" -P:" + name + ":" +  _).mkString("\n"))

  override def processOptions(options: List[String], error: String => Unit): Unit = {
    LinterOptions.parse(options) match {
     case Left(errorMessage) => error(errorMessage)
     case Right(linterOptions) =>
       Utils.linterOptions = linterOptions
    }
  }

  val inferred = mutable.HashSet[Position]() // Used for a scala 2.9 hack (can't find out which types are inferred)
  val intLiteralDiv = mutable.HashSet[Position]()

  private[this] object PreTyperComponent extends PluginComponent {
    val global = LinterPlugin.this.global
    import global._

    override val runsAfter = List("parser")

    val phaseName = "linter-parsed"

    private[this] val sealedTraits = mutable.Map.empty[Name, Tree]
    private[this] val usedTraits = mutable.Set.empty[Name]
    private[this] var inTrait = false
    private[this] def resetTraits(): Unit = {
      sealedTraits.clear()
      usedTraits.clear()
      inTrait = false
    }
    override def newPhase(prev: Phase): StdPhase = new StdPhase(prev) {
      override def apply(unit: global.CompilationUnit): Unit = {
        if (!unit.isJava) {
          resetTraits()
          nowarnPositions.clear
          new PreTyperTraverser(unit).traverse(unit.body)
          //println((sealedTraits, usedTraits))
          for (unusedTrait <- sealedTraits.filterNot(st => usedTraits.exists(_.toString == st._1.toString))) {
            //TODO: It might still be used in some type-signature somewhere... see scalaz
            warn(unusedTrait._2, UnextendedSealedTrait)(unit)
          }
        }
      }
    }

    class PreTyperTraverser(unit: CompilationUnit) extends Traverser {
      implicit val unitt: CompilationUnit = unit

      var superTraverse = true
      def catcher(): PartialFunction[Throwable, Unit] = {
        case e: NullPointerException => //Ignore
        case e: NoSuchMethodError => //Ignore
        case e: StackOverflowError => superTraverse = false
        case e: Exception => //TODO: Print details and ask user to report it
      }
      def finalizer(tree: Tree): Unit = {
        if (superTraverse) try { super.traverse(tree) } catch catcher
      }
      override def traverse(tree: Tree): Unit = try {
        superTraverse = true
        //if (showRaw(tree).contains("Hello"))println(showRaw(tree))
        tree match {
          /// quick hack for nowarnMergeNestedIfsPositions, see issue #18
          case If(_, apply @ Apply(_, _), Literal(Constant(())))
            if !(linterOptions.disabledWarningNames contains MergeNestedIfs.name)
            && { nowarnMergeNestedIfsPositions += apply.pos; false } => //Fallthrough

          /// Unused sealed traits (Idea by edofic)
          case ClassDef(mods, _name, _, Template(extendsList, _, body)) if !mods.isSealed && mods.isTrait =>
            for (Ident(traitName) <- extendsList) usedTraits += traitName
            inTrait = true
            for (stmt <- body) traverse(stmt)
            inTrait = false
            superTraverse = false; return

          // Typeparams (3rd param) are sometimes used for type-checking hacks
          case ClassDef(mods, name, Nil, Template(extendsList, _, body)) if mods.isSealed && mods.isTrait && !inTrait =>
            sealedTraits += name -> tree
            for (Ident(traitName) <- extendsList if traitName.toString != name.toString) usedTraits += traitName
            for (stmt <- body) traverse(stmt)
            superTraverse = false; return

          case ClassDef(_mods, _, _, Template(extendsList, _, body)) => //if mods.hasFlag(CASE) =>
            for (Ident(traitName) <- extendsList) usedTraits += traitName
            for (stmt <- body) traverse(stmt)
            superTraverse = false; return

          case ModuleDef(_mods, _name, Template(extendsList, _, body)) =>
            for (Ident(traitName) <- extendsList) usedTraits += traitName
            for (stmt <- body) traverse(stmt)
            superTraverse = false; return


          /// Warn on Nothing/Any or M[Nothing/Any] (idea by OlegYch)
          case ValDef(mods, _, tpe, _)
            if !mods.isParameter
            && tpe.toString == "" =>

            inferred += tpe.pos

          //case DefDef(mods: Modifiers, name, _, valDefs, typeTree, body) =>
            //if (name.toString != "" && !body.isEmpty && !mods.isAnyOverride) {
              /// Recursive call with exactly the same params
              //TODO: Currently doesn't cover shadowing or mutable changes of params, or the method shadowing/overriding
              /*for (
                call @ Apply(Ident(funcCall), funcParams) <- body;
                if (funcCall.toString == name.toString)
                && (funcParams.forall(_.isInstanceOf[Ident]))
                && (funcParams.map(_.toString).toList == params.map(_.toString).toList)
              ) warn(call, "Possible infinite recursive call. (Except if params are mutable, or the names are shadowed)")
              */
            //}

            /// Implicit method needs explicit return type
            //TODO: I'd add a bunch of exceptions for when the return type is actually clear:
            // when name includes name in a clear way
            // when the body is just new Type, or Type.apply(...): Type
            /*if (mods.isImplicit && typeTree.isEmpty && !(name.toString matches "((i?)to).+|.*(To|2)[A-Z].*")) {
              warn(tree, "Implicit method %s needs explicit return type" format name)
            }*/

          /// Detect literal integer division
          case Apply(Select(Literal(Constant(_num: Int)), op), List(Literal(Constant(_denom: Int))))
            if (op == nme.DIV || op == nme.MOD) =>

            if (op == nme.DIV && math.abs(_denom) > math.abs(_num)) {
              //TODO: should also be implemented in absinterpreter, but keep here because of expression simplifier
              //TODO: should all simplifiable expressions warn?
              warn(tree, OperationAlwaysProducesZero("integer division"))
            }

            intLiteralDiv += tree.pos

          /// Loss of precision on literals
          // Moved here because ConstantFolder in typechecker messes it up
          //TODO: "regular expression below can be used to screen the input string" http://docs.oracle.com/javase/6/docs/api/java/lang/Double.html#valueOf(java.lang.String)
          case treeLiteral @ Literal(Constant(literal)) if literal != null && literal != (()) && (literal match { case _: Double | _: Float => true; case _ => false; }) => //&& isFloatingPointType(tree) =>

            val tpe =
              (literal match {
                case _: Double => "Double"
                case _: Float => "Float"
                case _ => "floating point type" })

            val (strLiteral, actualLiteral) = {
                // Get the actual token from code
                val floatRegex = "[-+]?[0-9.]+([eE][-+]?[0-9]+)?[dfDF]?".r
                var token = ""
                try {
                  val pos = treeLiteral.pos
                  token = floatRegex.findPrefixMatchOf(pos.lineContent.substring(pos.column - 1)).get.toString
                  token.toDouble.toString // trigger NumberFormatException
                } catch {
                  case e: UnsupportedOperationException => // Happens if tree doesn't have a position
                  case e: NumberFormatException => // Could warn here, but I don't trust the above code enough
                  case e: MatchError => // Float regex failed
                  case e: Exception =>
                    //println(treeLiteral.pos.lineContent)
                    //e.printStackTrace
                }
                // TODO: this is more correct, but needs some work
                //("%.1080f".format(literal), token)
                (literal.toString, token)
              }

            try {
              // Ugly hack to get around a few different representations: 0.005, 5E-3, ...
              def cleanString(s: String): String = s.replaceAll("^[-+]|[.]|[Ee].*$|[fdFD-]*$|(0[.])?0*", "")

              if (!strLiteral.isEmpty && !actualLiteral.isEmpty
              && (strLiteral matches ".*[0-9].*") && (actualLiteral matches ".*[0-9].*")
              && cleanString(strLiteral) != cleanString(actualLiteral)
              && ((if (strLiteral.toDouble < 0) "-" else "") + actualLiteral) != strLiteral)
                warn(treeLiteral, PossibleLossOfPrecision("Literal cannot be represented exactly by "+tpe+". ("+(if (strLiteral.toDouble < 0) "-" else "") + actualLiteral+" != "+strLiteral.replaceAll("0+$", "0")+")"))

            } catch {
              case e: NumberFormatException => //Ignore
            }

          case _ => //Ignore
        }
      } catch catcher finally finalizer(tree)
    }
  }

  private[this] object PostTyperComponent extends PluginComponent {
    val global = LinterPlugin.this.global
    import global._

    override val runsAfter = List("typer")

    val phaseName = "linter-typed"

    override def newPhase(prev: Phase): StdPhase = new StdPhase(prev) {
      override def apply(unit: global.CompilationUnit): Unit = {
        if (!unit.isJava) {
          nowarnPositions.clear
          new PostTyperTraverser(unit).traverse(unit.body)
        }
      }
    }

    class PostTyperTraverser(unit: CompilationUnit) extends Traverser {
      implicit val unitt: CompilationUnit = unit
      import definitions.{ AnyClass, NothingClass, ObjectClass, Object_== }
      import definitions.{ OptionClass, SeqClass, TraversableClass, ListClass, StringClass }
      import definitions.{ DoubleClass, FloatClass, CharClass, ByteClass, ShortClass, IntClass, LongClass, BooleanClass }

      val MutableSeqLike: Symbol = rootMirror.getClassByName(newTermName("scala.collection.mutable.SeqLike"))

      val ExceptionClass = rootMirror.getClassByName(newTermName("java.lang.Exception"))

      val JavaConversionsModule: Symbol = rootMirror.getModuleByName(newTermName("scala.collection.JavaConversions"))
      val SeqLikeClass: Symbol = rootMirror.getClassByName(newTermName("scala.collection.SeqLike"))
      val SeqLikeContains: Symbol = SeqLikeClass.info.member(newTermName("contains"))
      val SeqLikeApply: Symbol = SeqLikeClass.info.member(newTermName("apply"))
      val MapFactoryClass = rootMirror.getClassByName(newTermName("scala.collection.generic.MapFactory"))
      val TraversableFactoryClass: Symbol = rootMirror.getClassByName(newTermName("scala.collection.generic.TraversableFactory"))
      val TraversableOnceClass: Symbol = rootMirror.getClassByName(newTermName("scala.collection.TraversableOnce"))
      val BigIntClass: Symbol = rootMirror.getClassByName(newTermName("scala.math.BigInt"))
      val JavaIntegerClass: Symbol = rootMirror.getClassByName(newTermName("java.lang.Integer"))

      val integerTypes = Set(JavaIntegerClass.tpe, BigIntClass.tpe, IntClass.tpe)

      val OptionGet: Symbol = OptionClass.info.member(nme.get)

      val IsInstanceOf = AnyClass.info.member(nme.isInstanceOf_)
      val AsInstanceOf = AnyClass.info.member(nme.asInstanceOf_)
      val ToString: Symbol = AnyClass.info.member(nme.toString_)

      def seqMemberType(seenFrom: Type): Type = {
        if (seenFrom.baseClasses.exists(_.tpe =:= SeqLikeClass.tpe))
          SeqLikeClass.tpe.typeArgs.head.asSeenFrom(seenFrom, SeqLikeClass)
        else
          OptionClass.tpe.typeArgs.head.asSeenFrom(seenFrom, OptionClass)
      }

      def isSubtype(x: Tree, y: Tree): Boolean = { x.tpe.widen <:< y.tpe.widen }
      def isSubtype(x: Tree, y: Type): Boolean = { x.tpe.widen <:< y.widen }

      def isIntegerType(tpe: Type): Boolean =
          (tpe <:< ByteClass.tpe
        || tpe <:< ShortClass.tpe
        || tpe <:< IntClass.tpe
        || tpe <:< LongClass.tpe)
      def isIntegerType(x: Tree): Boolean = isIntegerType(x.tpe.widen)

      def isFloatingPointType(tpe: Type): Boolean =
          (tpe <:< FloatClass.tpe
        || tpe <:< DoubleClass.tpe)
      def isFloatingPointType(x: Tree): Boolean = isFloatingPointType(x.tpe.widen)

      def methodImplements(method: Symbol, target: Symbol): Boolean =
        try { method == target || method.allOverriddenSymbols.contains(target) } catch { case e: NullPointerException => false }

      def isGlobalImport(selector: ImportSelector): Boolean = {
        selector.name == nme.WILDCARD && selector.renamePos == -1
      }

      def containsAnyType(tpe: Type): Boolean = (tpe =:= AnyClass.tpe || tpe.typeArgs.exists(_ =:= AnyClass.tpe))
      def containsNothingType(tpe: Type): Boolean = (tpe =:= NothingClass.tpe || tpe.typeArgs.exists(_ =:= NothingClass.tpe))

      def isOptionOption(t: Tree): Boolean =
        (t.tpe.widen.baseClasses.exists(_.tpe =:= OptionClass.tpe)
        && t.tpe.widen.typeArgs.exists(_.widen.baseClasses.exists(_.tpe =:= OptionClass.tpe)))

      def isLiteral(t: Tree): Boolean = t match {
        case Literal(_) => true
        case _ => false
      }
      def isIdent(t: Tree, recursive: Boolean = false): Boolean = t match {
        case Ident(_) => true
        case Select(t2, _TermName) if recursive => isIdent(t2, recursive)
        case _ => false
      }

      def getUsed(tree: Tree): Set[String] = (for (Ident(id) <- tree) yield id.toString).toSet
      def getAssigned(tree: Tree): Set[String] = {
        (for (Assign(Ident(id), _) <- tree) yield id.toString).toSet
        //TODO: non-local stuff (for (Apply(Select(id, setter), List(_)) <- tree; if setter.toString endsWith "_$eq") yield setter.dropRight(4)).toSet
      }

      //TODO: Still possible to have false positives...
      // val i = 4; val f: Double = 2.3 + List(1/i).sum
      def hasIntegerDivision(tree: Tree): Boolean = {
        def hasIntDiv: Boolean = tree exists { // <- this causes FP's
          case Apply(Select(param1, nme.DIV), List(param2)) if isIntegerType(param1) && isIntegerType(param2) => true
          case _ => false
        }
        def isFloatConversion: Boolean = tree match { // for Int expressions... .toDouble is implicitly added
          case Select(num, toFloat) if (toFloat.toString matches "to(Float|Double)") && isIntegerType(num) => true
          case _ => false
        }
        def hasIntOpFloat: Boolean = tree exists { //TODO: detect implicit widening, e.g. float + int
          case Apply(Select(param1, _op), List(param2))
            if (isIntegerType(param1) && isFloatingPointType(param2)) || (isFloatingPointType(param1) && isIntegerType(param2)) => true
          case _ => false
        }

        hasIntDiv && (isFloatConversion || hasIntOpFloat)
      }

      // Just a way to make the Tree/Name-String comparisons more readable
      abstract class RichToStr[T](n: T) {
        val nstr = n.toString
        def is(str: String): Boolean = nstr == str
        def isAny(strs: String*): Boolean = strs contains nstr
        def startsWith(str: String): Boolean = nstr startsWith str
        def startsWithAny(strs: String*): Boolean = strs.exists(nstr startsWith _)
        def endsWith(str: String): Boolean = nstr endsWith str
        def endsWithAny(strs: String*): Boolean = strs.exists(nstr endsWith _)
        def contains(str: String): Boolean = nstr contains str
        def containsAny(strs: String*): Boolean = strs.exists(nstr contains _)
      }
      implicit class richTree(n: Tree) extends RichToStr[Tree](n)
      implicit class richName(n: Name) extends RichToStr[Name](n)


      def identOrDefault(tree: Tree, default: String): String = {
        tree match {
          case Ident(_) if !tree.contains(".") =>

            tree.toString

          case Apply(scala_Predef_augmentString, List(str @ Ident(name)))
            if str.tpe <:< StringClass.tpe
            && (scala_Predef_augmentString endsWith "Predef.augmentString") =>

            name.toString

          case _ => default
        }
      }
      def identOrCol(tree: Tree): String = identOrDefault(tree, "col")
      def identOrOpt(tree: Tree): String = identOrDefault(tree, "opt")

      // Returns the string subtree of a string.length/size subtree
      def getStringFromLength(t: Tree): Option[Tree] = t match {
        case Apply(Select(str, Name("length")), Nil)
          if str.tpe <:< StringClass.tpe =>

          Some(str)

        case Select(Apply(scala_Predef_augmentString, List(str)), Name("size"))
          if str.tpe <:< StringClass.tpe
          && (scala_Predef_augmentString endsWith "Predef.augmentString") =>

          Some(str)

        case _ =>
          None
      }

      import java.math.MathContext
      val java_math_MathContext = rootMirror.getClassByName(newTermName("java.math.MathContext"))
      def getMathContext(t: Tree): Option[MathContext] = t match {
        case Apply(Select(New(_mathcontext), nme.CONSTRUCTOR), (Literal(Constant(precision: Int)) :: _roundingMode)) =>
          Some(new MathContext(precision)) // I think roundingMode is irrelevant, because the check will warn if there is any rounding
        case _ =>
          None
      }

      def isMath(t: Tree, name: String): Boolean = t match {
        case field
          if (field is "scala.math.`package`."+name)
          || (field is staticSelect("java", "lang.Math."+name))
          || (field is staticSelect("java", "lang.StrictMath."+name)) => true
        case Apply(func, _params)
          if (func is "scala.math.`package`."+name)
          || (func is staticSelect("java", "lang.Math."+name))
          || (func is staticSelect("java", "lang.StrictMath."+name)) => true
        case Select(Apply(scala_Predef_xWrapper, List(_arg)), func)
          if (scala_Predef_xWrapper.toString endsWith "Wrapper") && (func is name)
          && (t.tpe.widen weak_<:< DoubleClass.tpe) => true
        case _ => false
      }

      def isMathPow2(pow: Tree, detectSquareNumbers: Boolean = false): Boolean = pow match {
        case Apply(pow, List(_, Literal(Constant(2.0)))) if isMath(pow, "pow") => true   // math.pow(x, 2)
        case Apply(Select(id1, nme.MUL), List(id2)) if (id1 equalsStructure id2) => true // x*x
        case Literal(Constant(x: Int)) if (detectSquareNumbers) && (x > 1) && (math.sqrt(x.toDouble) == math.sqrt(x.toDouble).toInt) => true  // Square numbers
        case _ => /*println(showRaw(pow));*/ false
      }

      def isMathE(e: Tree): Boolean = e match {
        case Literal(Constant(e: Double))
          if (e >= 2.718281)
          && (e <= 2.718282) => true
        case _ if isMath(e, "E") => true
        case _ => false
      }

      def getAbs(t: Tree): Option[Tree] = t match {
        case Apply(abs, List(arg)) if isMath(abs, "abs") => Some(arg)
        case Select(Apply(scala_Predef_xWrapper, List(arg)), Name("abs"))
          if scala_Predef_xWrapper.toString endsWith "Wrapper" => Some(arg)
        case _ => None
      }

      def unwrapNumber(t: Tree): Tree = t match {
        case Apply(xWrapper, List(num)) if xWrapper.toString endsWith "Wrapper" => num
        case _ => t
      }

      def isEmptyCompare(x: Any, op: Name): Boolean = {
        ((x == 0 && (op == nme.EQ || op == nme.GT || op == nme.LE || op == nme.NE))
          || (x == 1 && (op == nme.LT || op == nme.GE)))
      }


      def isReturnStatement(t: Tree): Boolean = t match {
        case Return(_) => true
        case _ => false
      }

      var superTraverse = true
      def catcher(): PartialFunction[Throwable, Unit] = {
        case e: NullPointerException => //Ignore
        case e: NoSuchMethodError => //Ignore
        case e: StackOverflowError => superTraverse = false
        case e: Exception => //TODO: Print details and ask user to report it
      }
      def finalizer(tree: Tree): Unit = {
        if (superTraverse) try { super.traverse(tree) } catch catcher
      }

      object Name {
        def unapply(name: Name): Option[String] = Some(name.toString)
      }

      object FloatingPointNumber {
        def unapply(tree: Tree): Option[Tree] = {
          tree match {
            case Apply(xWrapper, List(FloatingPointNumber(x))) if xWrapper.toString endsWith "Wrapper" => Some(x)
            case t if isSubtype(tree, DoubleClass.tpe) || isSubtype(tree, FloatClass.tpe) => Some(t)
            case _ => None
          }
        }
      }

      override def traverse(tree: Tree): Unit = try {
        superTraverse = true
        //Workarounds for some cases
        tree match {
          ///Workaround: case class generated code triggers a lot of the checks...
          case ClassDef(mods, _, _, body) if mods.isCase => traverse(body); superTraverse = false; return
          ///Workaround: suppresse a null warning and a "remove the if" check for """case class A()""" - see case class unapply's AST)
          case If(Apply(Select(_, nme.EQ), List(Literal(Constant(null)))), Literal(Constant(false)), Literal(Constant(true))) => superTraverse = false; return
          ///Workaround: ignores "Assignment right after declaration..." in case class hashcode
          case DefDef(_, Name("hashCode"), _, _, _, Block(block, last)) if {
            (block :+ last) match {
              case ValDef(_, _id1, _, _) :: Assign(_id2, _) :: _ => true
              case _ => false
            }} => superTraverse = false; return

          //// Some numeric checks
          /// Detects some invariant conditions
          //TODO: Move to abstract interpreter, handle edge cases > / >=

          case Apply(Select(Apply(Select(term1, compareOp1), List(n1 @ Literal(Constant(num1)))), logicOp), List(Apply(Select(term2, compareOp2), List(n2 @ Literal(Constant(num2))))))
            if (tree.tpe <:< BooleanClass.tpe)
            && (term1 equalsStructure term2)
            && isIdent(term1, recursive = true)
            && (n1.tpe weak_<:< DoubleClass.tpe)
            && (n2.tpe weak_<:< DoubleClass.tpe)
            && {
              // It still thinks num is Any...
              def toDouble[T](x: T): Double = x match {
                case a: Double => a
                case a: Float  => a.toDouble
                case a: Long   => a.toDouble
                case a: Int    => a.toDouble
                case a: Short  => a.toDouble
                case a: Byte   => a.toDouble
                case _ => Double.NaN
              }

              def minmax(num: Double, compareOp: Name): (Double, Double) = compareOp match {
                case nme.GE | nme.GT => (num, Double.PositiveInfinity)
                case nme.LE | nme.LT => (Double.NegativeInfinity, num)
                case _               => (Double.NaN, Double.NaN)
              }

              val (min1, max1) = minmax(toDouble(num1), compareOp1)
              val (min2, max2) = minmax(toDouble(num2), compareOp2)

              if (!min1.isNaN && !max1.isNaN && !min2.isNaN && !max2.isNaN) logicOp match {
                case nme.ZAND
                  if (min1 > max2 || min2 > max1) => // Intervals don't cross
                  warn(tree, InvariantCondition(always = true, "return false"))
                case nme.ZOR
                  if (min1 < max2 && min2 < max1) // Intervals cross, and span whole space
                  && (math.min(min1, min2) == Double.NegativeInfinity)
                  && (math.max(max1, max2) == Double.PositiveInfinity) =>
                  warn(tree, InvariantCondition(always = true, "return true"))
                case _ =>
              }

              false
            } => //Fallthrough

          /*case Apply(Select(lhs, nme.EQ), List(rhs))
            if isSubtype(lhs, DoubleClass.tpe) || isSubtype(lhs, FloatClass.tpe) || isSubtype(rhs, DoubleClass.tpe) || isSubtype(rhs, FloatClass.tpe) =>

            warn(tree, "Exact comparison of floating point values is potentially unsafe.")*/

          /// Tip to use log1p(x) and expm1(x), instead of log(1+x) and exp(x)-1 -- see http://www.johndcook.com/blog/2010/06/07/math-library-functions-that-seem-unnecessary/
          //TODO: maybe make checks to protect against potentially wrong fixes, e.g. log1p(a + 1) or log1p(a - 1)
          // also, check 1-exp(x) and other negated versions
          case Apply(log, List(Apply(Select(Literal(Constant(1)), nme.ADD), _))) if isMath(log, "log") =>
            warn(tree, UseLog1p)
          case Apply(log, List(Apply(Select(_, nme.ADD), List(Literal(Constant(1)))))) if isMath(log, "log") =>
            warn(tree, UseLog1p)

          case Apply(Select(Apply(exp, _), nme.SUB), List(Literal(Constant(1)))) if isMath(exp, "exp") =>
            warn(tree, UseExpm1)
          case Apply(Select(Literal(Constant(-1)), nme.ADD), List(Apply(exp, _))) if isMath(exp, "exp") =>
            warn(tree, UseExpm1)

          /// Suggest using hypot instead of sqrt(a*a, b*b)
          case Apply(sqrt, List(Apply(Select(pow1, nme.ADD), List(pow2))))
            if isMath(sqrt, "sqrt")
            && isMathPow2(pow1, detectSquareNumbers = true)
            && isMathPow2(pow2, detectSquareNumbers = true) =>

            warn(tree, UseHypot)
          case Apply(sqrt, List(Select(Apply(Select(pow1, nme.ADD), List(pow2)), Name("toDouble")))) // Handle toDouble (implicit) converion
            if isMath(sqrt, "sqrt")
            && isMathPow2(pow1, detectSquareNumbers = true)
            && isMathPow2(pow2, detectSquareNumbers = true)  =>

            warn(tree, UseHypot)

          /// Suggest using cbrt instead of pow(a, 1/3)
          /// Suggest using sqrt instead of pow(a, 0.5)
          /// Unnecessary power (zero or one)
          case Apply(math_pow, List(_num, Literal(Constant(pow: Double)))) if isMath(math_pow, "pow") && {
            if (pow >= 0.3333332 && pow <= 0.3333334) warn(tree, UseCbrt)
            else if (pow == 0.5) warn(tree, UseSqrt)
            else if (pow == 1 || pow == 0 || pow.isNaN || pow.isInfinite) warn(tree, SuspiciousPow(pow))

            false
          } => //Fallthrough

          /// Suggest using exp instead of pow(E, a)
          case Apply(pow, List(e, _num))
            if isMath(pow, "pow")
            && isMathE(e) =>

            warn(tree, UseExp)

          /// Suggest using log10(x) instead of log(x)/log(10)
          case Apply(Select(Apply(log1, List(_num)), nme.DIV), List(Apply(log2, List(Literal(Constant(10.0))))))
            if isMath(log1, "log")
            && isMath(log2, "log") =>

            warn(tree, UseLog10)

          /// Suggest using x.abs instead of doing it manually with sqrt(x^2)
          case Apply(sqrt, List(pow2))
            if isMath(sqrt, "sqrt")
            && isMathPow2(pow2) =>

            warn(tree, UseAbsNotSqrtSquare)

          /// Use x.isNaN instead of (x != x)
          case Apply(Select(left, func), List(right))
            if isFloatingPointType(left)
            && (func == nme.EQ || func == nme.NE)
            && ((left equalsStructure right) || (right equalsStructure Literal(Constant(Double.NaN))) || (right equalsStructure Literal(Constant(Float.NaN)))) =>

            warn(tree, if (left equalsStructure right) UseIsNanNotSelfComparison else UseIsNanNotNanComparison)

          /// Use x.signum instead of doing it manually
          case pos @ Apply(Select(expr1, nme.DIV), List(expr2)) if ((expr1, expr2) match {
              case (expr1, Apply(abs, List(expr2))) if isMath(abs, "abs") && (expr1 equalsStructure expr2) => true
              case (Apply(abs, List(expr1)), expr2) if isMath(abs, "abs") && (expr1 equalsStructure expr2) => true
              case (expr1, Select(Apply(wrapper, List(expr2)), abs)) if (wrapper endsWith "Wrapper") && (abs is "abs") && (expr1 equalsStructure expr2) => true
              case (Select(Apply(wrapper, List(expr1)), abs), expr2) if (wrapper endsWith "Wrapper") && (abs is "abs") && (expr1 equalsStructure expr2) => true
              case _ => false
            }) =>

            warn(pos, UseSignum)

          case _ if {
            /// Possibly unsafe use of abs -- math.abs(Int.MinValue) == -2147483648
            getAbs(tree).foreach {
              case pos @ Apply(Select(rand, nextX), Nil)
                if (rand.tpe.widen.toString startsWith "scala.util.Random")
                && (nextX.isAny("nextInt", "nextLong")) =>

                val typeX = nextX.toString.stripPrefix("next")
                warn(pos, UnsafeAbs(s"Use ${nextX}(${typeX}.MaxValue) instead, as abs($typeX.MinValue) equals $typeX.MinValue"))

              case _ => //Ignore
            }

            false
          } => //Fallthrough

          /// BigDecimal checks
          // BigDecimal(0.1) //TODO: someday move to interpreter - detect this for known values
          case Apply(Select(bigDecimal, apply_valueOf), (treeLiteral @ Literal(Constant(literal))) :: paramTail)
            if ((bigDecimal is "scala.`package`.BigDecimal") || (bigDecimal endsWith "math.BigDecimal"))
            && (apply_valueOf isAny ("apply", "valueOf"))
            && (isFloatingPointType(treeLiteral) || treeLiteral.tpe.widen <:< StringClass.tpe) =>

            val isFloat = isFloatingPointType(treeLiteral)

            // actualLiteral is relevant only for double - compiler rounds it automatically
            val (strLiteral, actualLiteral) =
              if (isFloat) {
                // Get the actual token from code
                var token = ""
                try {
                  val pos = treeLiteral.pos
                  token = pos.lineContent.substring(pos.column - 1).takeWhile(_.toString matches "[-+0-9.edfEDF]").toLowerCase
                  if (!token.isEmpty && (token.last == 'f' || token.last == 'd')) token = token.dropRight(1)
                } catch {
                  case e: UnsupportedOperationException => // Happens if tree doesn't have a position
                  case e: NumberFormatException => // Could warn here, but I don't trust the above code enough
                }
                (literal.toString, token)
              } else {
                (literal.asInstanceOf[String], literal.asInstanceOf[String])
              }

            val mathContext =
              if (paramTail.size == 1 && paramTail.head.tpe <:< java_math_MathContext.tpe) {
                getMathContext(paramTail.head)
              } else {
                None
              }

            val improvement: String =
              if (isFloat) {
                if (strLiteral == actualLiteral && mathContext.isDefined) "Use a larger MathContext." else ""
                //else if (apply_valueOf is "apply") "Use a string constant."
                //else "Use a constructor with a string constant."
              } else {
                if (mathContext.isDefined) "Use a larger MathContext."
                else "Add a custom MathContext."
              }

            if (improvement != "") try {
              // Ugly hack to get around a few different representations: 0.005, 5E-3, ...
              def cleanString(s: String): String = s.replaceAll("^-|[.]|[Ee].*$|(0[.])?0*", "")

              val bd = if (mathContext.isDefined) BigDecimal(strLiteral, mathContext.get) else BigDecimal(strLiteral)
              if (cleanString(bd.toString) != cleanString(actualLiteral)) warn(treeLiteral, PossibleLossOfPrecision(improvement))
            } catch {
              case e: NumberFormatException =>
                warn(treeLiteral, BigDecimalNumberFormat)
            }

          // new java.math.BigDecimal(0.1)
          case Apply(Select(New(java_math_BigDecimal), nme.CONSTRUCTOR), List(Literal(Constant(_num: Double))))
            if java_math_BigDecimal is "java.math.BigDecimal" =>

            warn(tree, BigDecimalPrecisionLoss)

          // Warn about using NumericRange with floating point numbers because its broken:
          //   NumericRange is broken for floating point numbers (float, double), that means, when using it
          //   (collection methods: map, foreach, zip), it depends on which method is used for constructing
          //   the new collections. You basically can go two paths on NumericRange:
          //   1) The "apply" path:   @see scala.collection.immutable.NumericRange.locationAfterN
          //   2) The "foreach" path: @see scala.collection.immutable.NumericRange.foreach
          //
          //   The difference between these paths is that "foreach" accumulates values to obtain the current value
          //   (current += step), but "apply" will directly calculate the current value (start + (step * fromInt(n))).
          //   The apply path is more accurate. This means that the values on these path will diverge due to other
          //   rounding behavior.
          //
          //   For example:
          //
          //   val range = 0D to 1D by (1 / 7D)
          //
          //   1) Apply path
          //   range.iterator.toVector // uses apply in range.iterator in the end
          //   Vector(
          //     0.0,
          //     0.14285714285714285,
          //     0.2857142857142857,
          //     0.42857142857142855,
          //     0.5714285714285714,
          //     0.7142857142857142,
          //     0.8571428571428571,
          //     1.0
          //   )
          //
          //   2) Foreach path
          //   range.toVector // uses foreach at CanBuildFrom respectively ++= in the end
          //   Vector(
          //     0.0,
          //     0.14285714285714285,
          //     0.2857142857142857,
          //     0.42857142857142855,
          //     0.5714285714285714,
          //     0.7142857142857142,
          //     0.857142857142857,
          //     0.9999999999999998
          //   )
          //
          //   As one can see the last two values differ and iterator/apply is more accurate.
          case Apply(Select(partialRangeTree, Name("by")), List(FloatingPointNumber(_num)))
              if {
                val partialRangeClass = rootMirror.getClassByName(newTermName("scala.collection.immutable.Range.Partial"))

                (partialRangeTree.tpe.typeConstructor =:= partialRangeClass.tpe.typeConstructor &&
                 partialRangeTree.tpe.widen.typeArgs.headOption.exists(isFloatingPointType))
              }
            => warn(tree, FloatingPointNumericRange)

          /// Checks for self-assignments: a = a
          case Assign(left, right) if left equalsStructure right =>
            warn(left, ReflexiveAssignment)
          case Apply(Select(type1, varSetter), List(Select(type2, varName)))
            if (type1 equalsStructure type2) && (varSetter.toString == varName.toString+"_$eq") =>
            warn(type1, ReflexiveAssignment)

          /// Checks if you read from a file without closing it: scala.io.Source.fromFile(file).mkString
          //TODO: Only checks one-liners where you chain it - doesn't actually check if you close it
          //TODO: Possibly skips checking code in higher order functions later on
          case Select(fromFile, _) if fromFile startsWith "scala.io.Source.fromFile" =>
            warn(fromFile, CloseSourceFile)
            superTraverse = false; return

          /// Comparing with == on instances of different types: 5 == "5"
          //TODO: Scala 2.10 has a similar check "comparing values of types Int and String using `==' will always yield false"
          case Apply(eqeq @ Select(lhs, op @ (nme.EQ | nme.NE)), List(rhs))
            if methodImplements(eqeq.symbol, Object_==)
            && !isSubtype(lhs, rhs) && !isSubtype(rhs, lhs)
            && !(integerTypes.contains(lhs.tpe.widen) && integerTypes.contains(rhs.tpe.widen))
            && lhs.tpe.widen.toString != "Null" && rhs.tpe.widen.toString != "Null"
            && ((lhs.tpe.widen.toString.takeWhile(_ != '[') != rhs.tpe.widen.toString.takeWhile(_ != '[')) || {
              val higherReg = """.+?\[(.+)\]""".r
              val higherReg(lhsHigher) = lhs.tpe.widen.toString
              val higherReg(rhsHigher) = rhs.tpe.widen.toString

              def generic(s: String): Boolean =
                s.split(',').exists { t => (t.size == 1 || t.contains("_") || t.contains("?")) }

              ((lhsHigher != rhsHigher) && !generic(lhsHigher) && !generic(rhsHigher))
            }) =>

            warn(eqeq, UnlikelyEquality(lhs.tpe.widen.toString, rhs.tpe.widen.toString, if (op == nme.EQ) "==" else "!="))

          /// Warn against importing from collection.JavaConversions
          case Import(pkg, selectors) if (pkg.symbol == JavaConversionsModule) && (selectors exists isGlobalImport) =>
            warn(pkg, JavaConverters)

          /// Warn about wildcard imports (disabled)
          /*case Import(pkg, selectors) if selectors exists isGlobalImport =>
            //TODO: Too much noise - maybe it would be useful to non-IDE users if it printed
            // a nice selector import replacement, e.g. import mutable._ => import mutable.{ HashSet, ListBuffer }

            warn(pkg, "Wildcard imports should be avoided. Favor import selector clauses.")*/

          /// Collection.contains on different types: List(1, 2, 3).contains("2")
          case Apply(Select(col, Name("contains")), List(target))
            if !(target.tpe.widen weak_<:< seqMemberType(col.tpe.widen))
            && !(target.tpe =:= AnyClass.tpe)
            && (col.tpe.baseClasses.exists(c => c.tpe =:= SeqClass.tpe || c.tpe =:= OptionClass.tpe)) =>

            warn(tree, ContainsTypeMismatch(col.tpe.widen.toString, target.tpe.widen.toString))

          case Apply(TypeApply(Select(col, Name("contains")), _), List(target))
            if !(target.tpe.widen weak_<:< seqMemberType(col.tpe.widen))
            && !(target.tpe =:= AnyClass.tpe)
            && (col.tpe.baseClasses.exists(c => c.tpe =:= SeqClass.tpe || c.tpe =:= OptionClass.tpe)) =>

            warn(tree, ContainsTypeMismatch(col.tpe.widen.toString, target.tpe.widen.toString))

          /// Using toString on an Array
          case Apply(Select(array, Name("toString")), Nil)
            if (array.tpe.widen.toString startsWith "Array[") =>

            warn(tree, UnlikelyToString("Array"))

          /// Warn about using .asInstanceOf[T] (disabled)
          //TODO: false positives in case class A(), and in the interpreter init
          /*case aa @ Apply(a, List(b @ Apply(s @ Select(instanceOf, dd), ee))) if methodImplements(instanceOf.symbol, AsInstanceOf) =>
            //println((aa, instanceOf))
          case instanceOf @ Select(a, func) if methodImplements(instanceOf.symbol, AsInstanceOf) =>
            //TODO: too much noise, maybe detect when it's completely unnecessary
            warn(tree, "Avoid using asInstanceOf[T] (use pattern matching, type ascription, etc).")*/
          /// Warn about using .asInstanceOf[Number]
          case TypeApply(asInstanceOf @ Select(num, _asInstanceOf), _tpe)
            if methodImplements(asInstanceOf.symbol, AsInstanceOf)
            && !(num.tpe.widen <:< tree.tpe.widen || tree.tpe.widen <:< num.tpe.widen)
            && ((num.tpe.widen weak_<:< tree.tpe.widen) || (tree.tpe.widen weak_<:< num.tpe.widen)) =>
              warn(tree, NumberInstanceOf(tree.tpe.widen.toString))

          /*case TypeApply(instanceOf @ Select(a, funcName), t)
            if methodImplements(instanceOf.symbol, AsInstanceOf)
            && !(a.tpe.widen <:< tree.tpe.widen || tree.tpe.widen <:< a.tpe.widen)
            && !(a.tpe.typeSymbol.isAbstractType || tree.tpe.typeSymbol.isAbstractType)
            && !(a.tpe.widen.toString.matches(""".*\[((_\$?|\?)[0-9]+|[A-Z])\].*""") || tree.tpe.widen.toString.matches(""".*\[((_\$?|\?)[0-9]+|[A-Z])\].*"""))
            && !(a.tpe.widen.toString == "Object" || tree.tpe.widen.toString == "Object")
            && !(a.tpe.widen.toString == "Null") =>

            warn(tree.pos, pref+"The cast "+a.tpe.widen+".asInstanceOf["+tree.tpe.widen+"] is likely invalid.")*/

          /// Calling Option.get is potentially unsafe (disabled)
          //TODO: if (x.isDefined) func(x.get) / if (x.isEmpty) ... else func(x.get), etc. are false positives -- those could be detected in abs-interpreter
          /*case get @ Select(_, nme.get) if methodImplements(get.symbol, OptionGet) =>
            warn(tree, "Calling .get on Option will throw an exception if the Option is None.")*/

          /// Nag about using null (disabled)
          //TODO: Too much noise - limit in some way
          /*case Literal(Constant(null)) =>
            warn(tree, "Using null is considered dangerous, use Option.")*/

          /// TypeToType ... "hello".toString, 5.toInt, List(1, 2, 3).toList ...
          case Select(fTpe, tTpe)
            if !fTpe.isInstanceOf[This] // calling toX inside X
            && tTpe.toString.startsWith("to") && {
              val fromTpe = fTpe.tpe.widen.toString
              val toTpe = tTpe.toString.drop(2)
              val targetTpe = tree.tpe.widen.toString

              // TODO deal with false positives from this, see commented test
              // oh my...
              /// Array.toArray: scala.collection.mutable.ArrayOps[Int] => [U >: Int](implicit evidence$1: scala.reflect.ClassTag[U])Array[U]
              /// Map.toMap: scala.collection.mutable.Map[Int,Int] => [T, U](implicit ev: <:<[(Int, Int),(T, U)])scala.collection.immutable.Map[T,U])
              def cleanTypeStr(tpeStr: String) =
                (tpeStr
                  .replaceFirst("^\\[.*?\\](=>)? ?", "")
                  .replaceFirst("^\\(.*?\\)", "")
                  .takeWhile(_ != '[')
                  .replaceAll(".*[.]", "")
                  .stripPrefix("Rich")
                  .stripSuffix("Ops"))

              if (toTpe == cleanTypeStr(targetTpe) && toTpe == cleanTypeStr(fromTpe)) {
                if (toTpe == "Set" || toTpe == "Map") {
                  // on mutable Map and Set conversion returns immutable
                  !fromTpe.startsWith("scala.collection.mutable.")
                } else {
                  true
                }
              } else {
                false
              }

            } =>

            warn(tree, TypeToType(tTpe.toString.stripPrefix("to")))

          //// String checks
          /// Repeated string literals (disabled)
          /*case Literal(Constant(str: String)) =>
            //TODO: String interpolation gets broken down into parts and causes false positives
            //TODO: a quick benchmark showed string literals are actually more optimized than almost anything else, even final vals
            val threshold = 4

            stringLiteralCount(str) += 1
            if (stringLiteralCount(str) == threshold && !(stringLiteralFileExceptions.contains(unit.source.toString)) && !(str.matches(stringLiteralExceptions))) {
              //TODO: Too much noise :)
              warn(tree, """String literal """"+str+"""" appears multiple times.""")
            }*/

          /// str.substring(len) -> str.drop(len) (disabled)
          /*case Apply(Select(str, substring), List(strLen))
            if str.tpe <:< definitions.StringClass.tpe
            && substring.toString == "substring"
            && getStringFromLength(strLen).isDefined =>

            warn(tree, "Use x.take(len), instead of x.substring(len)")*/

          /// str.substring(0, str.length - len) -> str.dropRight(len) (disabled)
          /*case Apply(Select(str1, substring), List(Literal(Constant(0)), Apply(Select(str1Len, nme.SUB), List(str2Len))))
            if str1.tpe <:< definitions.StringClass.tpe
            && substring.toString == "substring"
            && getStringFromLength(str1Len).isDefined && getStringFromLength(str2Len).isDefined
            && (str1 equalsStructure getStringFromLength(str1Len).get) =>

            warn(tree, "Use x.dropRight(len), instead of x.substring(x.length-len)")*/

          /// Processing a constant string: "hello".size (disabled)
          /*case Apply(Select(pos @ Literal(Constant(s: String)), func), params) =>
            func.toString match {
              case "$plus"|"equals"|"$eq$eq"|"toCharArray"|"matches"|"getBytes" => //Ignore
              case "length" => warn(pos, "Taking the length of a constant string")
              case _        => warn(pos, "Processing a constant string")
            }
          case Select(Apply(Select(predef, augmentString), List(pos @ Literal(Constant(s: String)))), size)
            if predef is "scala.this.Predef" && augmentString.toString == "augmentString" && size.toString == "size" =>
            warn(pos, "Taking the size of a constant string")*/

          //// Pattern Matching checks
          case Match(pat, cases)
            if (pat match {
              case Typed(_, t) if !t.contains("switch") => false; // for scala.annotation.switch
              case _ => true })
            && pat.tpe.toString != "Any @unchecked"
            && cases.size >= 2 =>
            //Workaround: "Any @unchecked" seems to happen on the matching structures of actors - and all cases return true
            //Workaround: Typed (or Annotated) seems to happen in for loop pattern matching, which doesn't work right for at least for checkUsage

            /// Pattern Matching on a constant value
            //TODO: move to abs-interpreter
            if (isLiteral(pat)) {
              /*val returnValue =
                cases
                  .map { ca => (ca.pat, ca.body) }
                  .find { case (Literal(Constant(c)), _) => c == a; case _ => false}
                  .map { _._2 }
                  .orElse { if (cases.last.pat.toString == "_") Some(cases.last.body) else None }
                  .map { s => " will always return " + s }
                  .getOrElse("")*/

              warn(tree, PatternMatchConstant)
            }

            var optionCase, booleanCase = false
            //TODO: Hacky hack hack -_-, sorry. use tpe <:< definitions.xxx.tpe
            val (optionCaseReg, booleanCaseReg) = ("(Some[\\[].*[\\]]|None[.]type)", "Boolean[(](true|false)[)]")
            def checkCase(caseTree: CaseDef): Unit = {
              val caseStr = caseTree.pat.toString
              val caseTypeStr = caseTree.pat.tpe.toString
              //println((caseStr, caseTypeStr))

              optionCase |= (caseTypeStr matches optionCaseReg)
              booleanCase |= (caseTypeStr matches booleanCaseReg)
            }
            def printCaseWarning(): Unit = {
              if (cases.size == 2) {
                if (optionCase) {
                  /// Checks if pattern matching on Option -- see: http://blog.tmorris.net/posts/scalaoption-cheat-sheet/ (disabled)
                  //TODO: too much noise, and some cases are perfectly fine - try detecting all the exact cases from link
                  //warn(tree, "There are probably better ways of handling an Option.")
                } else if (booleanCase) {
                  /// Checks if pattern matching on Boolean
                  //TODO: case something => ... case _ => ... is also an if in a lot of cases
                  warn(tree, PreferIfToBooleanMatch)
                }
              }
            }

            /// Checking for duplicate case bodies and suggests merging
            // Check only if isLiteral(c.pat), because other types usually cannot be easily merged
            //TODO: could also work for literal local vals
            case class Streak(streak: Int, tree: CaseDef)
            var streak = Streak(0, cases.head)
            def checkStreak(c: CaseDef): Unit = {
              if ((c.body != EmptyTree) && (c.body equalsStructure streak.tree.body) && isLiteral(c.pat) && (c.guard == EmptyTree && streak.tree.guard == EmptyTree)) {
                streak = Streak(streak.streak + 1, c)
              } else {
                printStreakWarning()
                streak = Streak(1, c)
              }
            }
            def printStreakWarning(): Unit = {
              if (streak.streak == cases.size) {
                // This one always turns out to be a false positive :)
                //warn(tree, "All "+cases.size+" cases will return "+cases.head.body+", regardless of pattern value")
              } else if (streak.streak > 1) {
                warn(streak.tree.body, IdenticalCaseBodies(streak.streak.toString))
              }
            }

            /// Checking for unused variables in pattern matching (disabled)
            //def checkUsage(c: CaseDef): Unit = {
              //TODO: use for self-testing from time to time - ~100 warnings currently :/
              /*val binds = for (b @ Bind(name, _) <- c.pat; if !name.toString.startsWith("_")) yield (b, name.toString)
              for (unused <- binds.filter { case (b, name) => !abstractInterpretation.isUsed(c, name)}) {
                println(showRaw(pat))
                warn(unused._1, "Unused value in pattern matching, use _ instead. (or prefix with _ to get rid of me)")
              }*/
            //}

            /// Detect some unreachable cases
            //TODO: move to abs. interpreter to detect impossible guards
            //TODO: if there is a case (x, y) without a guard, it will make a latter case (x, y) with a guard unreachable
            val pastCases = mutable.ListBuffer[CaseDef]()
            def checkUnreachable(c: CaseDef): Unit = {
              // Adapted from scala/reflect/internal/Trees.scala to cover wildcards in CaseDef
              def correspondsWildcardStructure(thiz: CaseDef, that: CaseDef): Boolean = {
                val wildcards = mutable.HashSet[(Name, Name)]() // Enumerate wildcard aliases

                def correspondsStructure(thiz: Tree, that: Tree): Boolean = {
                  (thiz eq that) || ((thiz.productArity == that.productArity) && {
                    def equals0(this0: Any, that0: Any): Boolean = (this0, that0) match {
                      case (x: Name, y: Name) if wildcards.contains((x, y)) =>
                        true
                      case (x: Tree, y: Tree) =>
                        (x eq y) || correspondsStructure(x, y)
                      case (xs: List[_], ys: List[_]) =>
                        (xs corresponds ys)(equals0)
                      case _ =>
                        this0 == that0
                    }
                    def compareOriginals(): Boolean = (thiz, that) match {
                      case (x: TypeTree, y: TypeTree) if x.original != null && y.original != null =>
                        correspondsStructure(x.original, y.original)
                      case _ =>
                        true
                    }

                    ((thiz, that) match {
                      case (b1 @ Bind(x, Ident(nme.WILDCARD)), b2 @ Bind(y, Ident(nme.WILDCARD))) =>
                        if (b1.tpe =:= b2.tpe) {
                          wildcards += ((x, y))
                          true
                        } else {
                          false
                        }
                      case _ =>
                        thiz.productIterator zip that.productIterator forall { case (x, y) => equals0(x, y) }
                    }) && compareOriginals()
                  })
                }

                (correspondsStructure(thiz.pat, that.pat) && correspondsStructure(thiz.guard, that.guard))
              }

              if (pastCases exists { p => correspondsWildcardStructure(p, c) })
                warn(c, IdenticalCaseConditions)
              else {
                val nonUnitResult = (pastCases += c)
              }
            }

            for (c <- cases) {
              checkCase(c)
              checkStreak(c)
              //checkUsage(c)
              checkUnreachable(c)
            }

            printStreakWarning()
            printCaseWarning()

            def orderOption(cases: List[CaseDef]) = cases match {
              case List(none @ CaseDef(scala_None, EmptyTree, y), some) if scala_None is "scala.None" => List(some, none)
              case x => x
            }

            def matchOption(cases: List[CaseDef]) = cases match {
              case List(CaseDef(Apply(scala_Some @ TypeTree(), List(Bind(x1, Ident(nme.WILDCARD)))), EmptyTree, x2), CaseDef(scala_None1, EmptyTree, scala_None2))
                if (scala_Some.original is "scala.Some") &&
                   (scala_None1 is "scala.None") &&
                   (scala_None2 is "scala.None") &&
                   (x1.toString == x2.toString) =>

                warn(tree, UseOptionFlattenNotPatMatch)

              case List(CaseDef(Apply(scala_Some1 @ TypeTree(), List(Bind(x1, Ident(nme.WILDCARD)))), EmptyTree, Apply(TypeApply(Select(scala_Some2, Name("apply")), _), List(x2))), CaseDef(scala_None, EmptyTree, y))
                if (scala_Some1.original is "scala.Some") &&
                   (scala_Some2 is "scala.Some") &&
                   (scala_None is "scala.None") &&
                   (x1.toString == x2.toString) =>

                warn(tree, UseOrElseNotPatMatch(y.toString))

              case List(CaseDef(Apply(scala_Some @ TypeTree(), List(Bind(x1, Ident(nme.WILDCARD)))), EmptyTree, x2), CaseDef(scala_None, EmptyTree, y))
                if (scala_Some.original is "scala.Some") &&
                   (scala_None is "scala.None") &&
                   (x1.toString == x2.toString) =>

                warn(tree, UseGetOrElseNotPatMatch(y.toString))

              case List(CaseDef(Apply(scala_Some1 @ TypeTree(), List(Bind(_, Ident(nme.WILDCARD)))), EmptyTree, Apply(TypeApply(Select(scala_Some2, Name("apply")), _), List(y))), CaseDef(scala_None1, EmptyTree, scala_None2))
                if (scala_Some1.original is "scala.Some") &&
                   (scala_Some2 is "scala.Some") &&
                   (scala_None1 is "scala.None") &&
                   (scala_None2 is "scala.None") =>

                warn(tree, UseOptionMapNotPatMatch(y.toString))

              case List(CaseDef(Apply(scala_Some @ TypeTree(), List(Bind(_, Ident(nme.WILDCARD)))), EmptyTree, y), CaseDef(scala_None1, EmptyTree, scala_None2))
                if (scala_Some.original is "scala.Some") &&
                   (scala_None1 is "scala.None") &&
                   (scala_None2 is "scala.None") =>

                warn(tree, UseOptionFlatMapNotPatMatch(y.toString))

              case List(CaseDef(Apply(scala_Some @ TypeTree(), List(Bind(_, Ident(nme.WILDCARD)))), EmptyTree, y), CaseDef(scala_None, EmptyTree, unit))
                if (scala_Some.original is "scala.Some") &&
                   (scala_None is "scala.None") &&
                   (unit is "()") =>

                warn(tree, UseOptionForeachNotPatMatch(y.toString))

              case List(CaseDef(Apply(scala_Some @ TypeTree(), List(Bind(x1, Ident(nme.WILDCARD)))), EmptyTree, Literal(Constant(true))), CaseDef(scala_None, EmptyTree, Literal(Constant(false))))
                if (scala_Some.original is "scala.Some") =>

                warn(tree, UseOptionIsDefinedNotPatMatch)

              case List(CaseDef(Apply(scala_Some @ TypeTree(), List(Bind(x1, Ident(nme.WILDCARD)))), EmptyTree, Literal(Constant(false))), CaseDef(scala_None, EmptyTree, Literal(Constant(true))))
                if (scala_Some.original is "scala.Some") =>

                warn(tree, UseOptionIsEmptyNotPatMatch)

              case List(CaseDef(Apply(scala_Some @ TypeTree(), List(Bind(x1, Ident(nme.WILDCARD)))), EmptyTree, y), CaseDef(scala_None, EmptyTree, Literal(Constant(false))))
                if (scala_Some.original is "scala.Some") =>

                warn(tree, UseOptionExistsNotPatMatch(y.toString))

              case List(CaseDef(Apply(scala_Some @ TypeTree(), List(Bind(x1, Ident(nme.WILDCARD)))), EmptyTree, y), CaseDef(scala_None, EmptyTree, Literal(Constant(true))))
                if (scala_Some.original is "scala.Some") =>

                warn(tree, UseOptionForallNotPatMatch(y.toString))
            }
            matchOption(orderOption(cases))
          //// If checks
          /// Same expression on both sides of comparison.
          case Apply(Select(left, func), List(right))
            if (func.toString matches "[$](greater|less|eq|bang)([$]eq)?") && (left equalsStructure right) =>

            warn(tree, ReflexiveComparison)

          /// Same expression on both sides of a boolean operation.
          case Apply(Select(left, func), List(right))
            if (func == nme.ZOR || func == nme.OR || func == nme.XOR || func == nme.ZAND || func == nme.AND/* || func == nme.EQ || func == nme.NE*/)
            && (left.symbol == null || !left.symbol.isMethod)
            && (left equalsStructure right)
            && tree.tpe.widen <:< BooleanClass.tpe =>

            warn(tree, ReflexiveComparison)

          /// Yoda conditions -- http://www.codinghorror.com/blog/2012/07/new-programming-jargon.html
          //Workaround: ranges, where it's acceptable, e.g. if (6 < a && a < 10) ...
          case Apply(Select(
            yoda @ Apply(Select(Literal(Constant(_)), func1), List(notLiteral1)), _logicOp),
            List(Apply(Select(notLiteral2, func2), List(_arg2))))
            if (func1.toString matches "[$](greater|less)([$]eq)?") && !isLiteral(notLiteral1)
            && (func2.toString matches "[$](greater|less)([$]eq)?") && (notLiteral1 equalsStructure notLiteral2)
            && (func1.toString.take(5) == func2.toString.take(5)) => // cheap way of saying < and <= can appear in range together

            nowarnPositions += yoda.pos

          case Apply(Select(Literal(Constant(_)), func), List(notLiteral))
            if (func.toString matches "[$](greater|less|eq)([$]eq)?")
            && !isLiteral(notLiteral)
            && !(notLiteral is "x") //Workaround: for synthetic "abc".filter('a'==) ... TODO: skips all yoda conditions with x
            && !(notLiteral is "x$1") => //Workaround: for synthetic "abc".filter('a' == _)

            warn(tree, YodaConditions)

          /// Unnecessary Ifs
          case If(cond, Literal(Constant(true)), Literal(Constant(false))) =>
            warn(cond, UseConditionDirectly())
          case If(cond, Literal(Constant(false)), Literal(Constant(true))) =>
            warn(cond, UseConditionDirectly(negated = true))
          case If(cond, Assign(id1, _), Assign(id2, _)) //TODO: .this workaround for object-local variables sucks... false negatives
            if (id1.toString == id2.toString) && !(id1.toString contains ".this") =>
            warn(cond, UseIfExpression(id1.toString))
          case If(cond, Apply(Select(id1, setter1), List(_)), Apply(Select(id2, setter2), List(_)))
            if (setter1 endsWith "_$eq") && (setter2 endsWith "_$eq") && (id1.toString == id2.toString) && !(id1.toString contains ".this") =>
            warn(cond, UseIfExpression(id1.toString))
          // TODO showRaw hack - find flag for macro code instead
          case If(cond, Block(block, ret), Block(_, _)) if (isReturnStatement(ret) || block.exists(isReturnStatement)) && !showRaw(tree).contains("$macro$") => // Idea from oclint
            warn(cond, UnnecessaryElseBranch)
          case If(_cond, a, b) if (a equalsStructure b) && (a.children.nonEmpty) =>
            //TODO: empty if statement (if (...) { }) triggers this - issue warning for that case?
            //TODO: test if a single statement counts as children.nonEmpty
            warn(a, DuplicateIfBranches)
          case If(_cond1, a, If(_cond2, b, c))
            if (a.children.nonEmpty && ((a equalsStructure b) || (a equalsStructure c)))
            || (b.children.nonEmpty && (b equalsStructure c)) =>
            //TODO: could be made recursive, but probably no need

            warn(a, DuplicateIfBranches)

          /// Find repeated (sub)conditions in if-else chains, that will never hold
          // Caches conditions separated by OR, and checks all subconditions separated by either AND or OR
          case If(_cond, t, e) if {
            def getSubConds(cond: Tree)(op: Name): List[Tree] =
              List(cond) ++ (cond match {
                case Apply(Select(left, opp), List(right)) if op == opp =>
                  getSubConds(left)(op) ++ getSubConds(right)(op)
                case _ =>
                  Nil
              })
            val conds = mutable.ListBuffer[Tree]()
            def elseIf(tree: Tree): Unit = {
              tree match {
                case If(cond, t, e) =>
                  val subCondsOr = getSubConds(cond)(nme.ZOR)
                  val subCondsAnd = getSubConds(cond)(nme.ZAND)

                  for (newCond <- (subCondsOr ++ subCondsAnd); oldCond <- conds) {
                    if ((newCond equalsStructure oldCond) && !(newCond.toString.toLowerCase contains "random")) {
                      warn(newCond, IdenticalIfElseCondition)
                    }
                  }

                  conds ++= subCondsOr

                  elseIf(e)

                  /// Detects some nonsensical if expressions of the type: if (a == b || ...) a else b
                  (subCondsOr ++ subCondsAnd) filter {
                    case Apply(Select(a, op), List(b))
                      if (op == nme.EQ || op == nme.NE)
                      && (((a equalsStructure t) && (b equalsStructure e)) || ((a equalsStructure e) && (b equalsStructure t))) => true
                    case _ => false
                  } foreach { subCond =>
                    warn(subCond, InvariantCondition(always = true, "cause the same return value"))
                  }
                case _ => //Ignore
              }
            }
            elseIf(tree)

            false
          } => //Fallthrough

          /// if (cond1) { if (cond2) { ... } } is the same as if (cond1 && cond2) { ... }
          case If(_cond1, iff @ If(_cond2, _body, else1), else2)
            if (else1 equalsStructure else2)
            && !(nowarnMergeNestedIfsPositions contains iff.pos) =>

            warn(tree, MergeNestedIfs)

          /// ifdowhile loop (idea by OpenSSL Valhalla Rampage) :)
          case If(cond1, LabelDef(doWhile1, List(), Block(_body, If(cond2, Apply(Ident(doWhile2), List()), Literal(Constant(()))))), Literal(Constant(())))
            if (cond1 equalsStructure cond2)
            && (doWhile1.toString startsWith "doWhile")
            && (doWhile1.toString == doWhile2.toString) =>

            warn(tree, IfDoWhile)

          //// Multiple-statement checks
          case Block(init, last) =>
            val block = init :+ last

            /// Check for unused variable values
            sealed trait AssignStatus
            case object Unknown extends AssignStatus
            case object Defined extends AssignStatus
            case object Unused extends AssignStatus
            case object Used extends AssignStatus

            val assigns = mutable.HashMap[Name, AssignStatus]() withDefaultValue Unknown
            def checkAssigns(tree: Tree, onlySetUsed: Boolean): Unit = {
              tree match {
                case ClassDef(_, _, _, _) | DefDef(_, _, _, _, _, _) =>
                  // see issue 21 - skip assignments that are not executed immediately

                //TODO: It could check if it gets set in all branches - Ignores currently
                case If(_, _, _) | Match(_, _) =>
                  for (t <- tree.children) checkAssigns(t, onlySetUsed = true)

                case ValDef(mods, id, _, right) if mods.isMutable =>
                  //TODO: shadowing warning doesn't work, even if I make sure each tree is visited once, and even if I don't traverse inner Blocks
                  //if (assigns contains id) warn(tree, "Variable "+id.toString+" is being shadowed here.")
                  checkAssigns(right, onlySetUsed)

                  assigns(id) =
                    if((right.tpe.widen weak_<:< DoubleClass.tpe)
                    || (right.tpe.widen <:< BooleanClass.tpe)
                    || (right.tpe.widen <:< StringClass.tpe)
                    || (right isAny ("null", "scala.None")))
                      Defined //Ignore these initial values
                    else
                      Unused

                case Assign(Ident(id), right) =>
                  checkAssigns(right, onlySetUsed)
                  if (!onlySetUsed) assigns(id) match {
                    case Unknown => //Ignore
                    case Defined => assigns(id) = Unused
                    case Used    => assigns(id) = Unused
                    case Unused  => warn(tree, VariableAssignedUnusedValue(id.toString))
                  }

                case Ident(id) =>
                  assigns(id) = Used

                case tree =>
                  //for (Ident(id) <- tree; if assigns(id) == Unused()) assigns(id) == Used()
                  for (t <- tree.children) checkAssigns(t, onlySetUsed)
              }
            }

            /// Warning on exiting a block with unused values (broken/disabled)
            /*val unused = (assigns filter { _._2 == Unused() } map { _._1.toString.trim } mkString ", ")
            if (!unused.isEmpty) warn(block.last, "Variable(s) "+unused+" have an unused value before here.")*/

            for (stmt <- block) { checkAssigns(stmt, onlySetUsed = false) }

            /// Unthrown exception
            //TODO: only finds instances smack in the middle of blocks, and only new Exception somethings
            for (stmt <- init) {
              if (stmt.tpe.widen <:< ExceptionClass.tpe) stmt match {
                case Apply(Select(New(_), nme.CONSTRUCTOR), Nil) => warn(stmt, UnthrownException)
                case _ => //Ignore
              }
            }

            //// Checks on two subsequent statements
            (block zip block.tail) foreach {
              /// Probably mistake in swaping two variables: a = b, b = b
              //TODO: there could be other expressions between the assigns
              case (Assign(id1, id2), Assign(id2_, id1_)) if (id1 equalsStructure id1_) && (id2 equalsStructure id2_) =>
                warn(id1_, MalformedSwap)

              /// "...; val x = value; x }" at the end of a method (disabled)
              //TODO: disabled - I usually have commented debug outputs in between these
              //TODO: this could be generalized in the new unused value code above
              /*case (v @ ValDef(_, id1, _, _), l @ Ident(id2)) if id1.toString == id2.toString && (l eq last) =>
                warn(v, "You don't need that temp variable.")*/

              /// Doing the same thing twice
              case (if1 @ If(cond1, _, _), _if2 @ If(cond2, _, _))
                if (cond1 equalsStructure cond2)
                && (if1 match {
                  case If(Ident(_), _, _) => //Ignore single booleans - probably debug/logging
                    false
                  case If(Select(Ident(_), nme.UNARY_!), _, _) => //Ignore single booleans - probably debug/logging
                    false
                  case If(cond, t, f) if (getUsed(cond) & (getAssigned(t) ++ getAssigned(f))).nonEmpty => //Ignore if assigning variables which appear in condition
                    false
                  case If(_, notBlock, empty)
                    if !notBlock.isInstanceOf[Block] && !notBlock.isInstanceOf[ValDef]
                    && (empty equalsStructure Literal(Constant(()))) => //Ignore simple things - probably debug/logging
                    false
                  case _ =>
                    true
                }) =>

                warn(cond2, IdenticalIfCondition)

              case (s1, s2)
                if (s1 equalsStructure s2)
                && !(s1 is staticSelect("scala", "Predef.println()"))
                && !(s1.toString contains "Next") && !(s1.toString contains "next") =>

                nowarnPositions += s2.pos
                warn(s1, IdenticalStatements)

              case _ =>
            }

          /// Literal negative index for a collection (obsolete?)
          case Apply(Select(_seq, _apply), List(Literal(Constant(index: Int))))
            if methodImplements(tree.symbol, SeqLikeApply) && index < 0 =>
            warn(tree, IndexingWithNegativeNumber)

          /// Literal division by zero (obsoleted by abs-int? not for all types, sadly...)
          // Can't always check double/float, as even the parser will change it to Infinity
          case Apply(Select(num, op), List(denomLiteral @ Literal(Constant(denom))))
            if (op == nme.DIV || op == nme.MOD)
            && (num.tpe.widen weak_<:< DoubleClass.tpe)
            && (denomLiteral.tpe.widen weak_<:< DoubleClass.tpe)
            && (denom == 0 || denom == 1) =>

            if (denom == 0) {
              warn(tree, DivideByZero)
            } else if (denom == 1) {
              if (op == nme.DIV) warn(tree, DivideByOne)
              else if (op == nme.MOD && (num.tpe.widen weak_<:< LongClass.tpe)) warn(tree, ModuloByOne)
            }
          case Apply(Select(numLiteral @ Literal(Constant(num)), op), List(denom))
            if (op == nme.DIV || op == nme.MOD)
            && (numLiteral.tpe.widen weak_<:< DoubleClass.tpe)
            && (denom.tpe.widen weak_<:< DoubleClass.tpe)
            && (num == 0) =>

            warn(tree, ZeroDivideBy)

          /// Option of an Option
          //TODO: make stricter if you want, but Ident(_) could get annoying if someone out there is actually using this :)
          case ValDef(_, _, _, value) if isOptionOption(value) =>
            warn(tree, OptionOfOption)

          /// Inferred type Nothing, Any, M[Nothing], or M[Any] (idea by OlegYch)
          case ValDef(mods, name, tpe, body)
            if !mods.isParameter
            && !(name.toString.trim matches "res[0-9]+") //Workaround: for REPL
            && !(name.toString.trim == "$result") //Workaround: for REPL/Tests
            && ((tpe.toString contains "Any") || (tpe.toString contains "Nothing")) // Gets rid of Stuff[_]
            && (containsAnyType(tpe.tpe) || containsNothingType(tpe.tpe))
            && (inferred contains tpe.pos)
            && !(body.isInstanceOf[New]) =>

            body match {
              case Apply(Select(New(_), nme.CONSTRUCTOR), _) =>
              case TypeApply(Select(_, Name("asInstanceOf")), _) =>
              case Apply(TypeApply(Select(_collection, Name("apply")), types), _elems)
                if types.exists(t => t.isInstanceOf[TypeTree] && t.asInstanceOf[TypeTree].original != null) =>
              case Ident(_) =>
              case _ => warn(tree, UndesirableTypeInference(tpe.tpe.toString))
            }

          /// Putting null into Option (idea by Smotko)
          case DefDef(_, _, _, _, tpe, body) if (tpe.toString matches "Option\\[.*\\]") &&
            (body match {
              case n @ Literal(Constant(null)) => warn(n, AssigningOptionToNull); true
              case Block(_, n @ Literal(Constant(null))) => warn(n, AssigningOptionToNull); true
              case _ => false
            }) => //Ignore
          case ValDef(_, _, tpe, body) if (tpe.toString matches "Option\\[.*\\]") &&
            (body match {
              case n @ Literal(Constant(null)) => warn(n, AssigningOptionToNull); true
              case Block(_, n @ Literal(Constant(null))) => warn(n, AssigningOptionToNull); true
              case _ => false
            }) => //Ignore
          case Assign(left, right) if (left.tpe.toString matches "Option\\[.*\\]") &&
            (right match {
              case n @ Literal(Constant(null)) => warn(n, AssigningOptionToNull); true
              case Block(_, n @ Literal(Constant(null))) => warn(n, AssigningOptionToNull); true
              case _ => false
            }) => //Ignore

          /// Null checking instead of Option wrapping
          case If(Apply(Select(left, op), List(Literal(Constant(null)))), t, f)
            if (op == nme.EQ && (t is "scala.None") && (f match {
              case Apply(TypeApply(scala_Some_apply, _), List(some)) if (left equalsStructure some) && (scala_Some_apply startsWith "scala.Some.apply") => true
              case _ => false
            }))
            || (op == nme.NE && (f is "scala.None") && (t match {
              case Apply(TypeApply(scala_Some_apply, _), List(some)) if (left equalsStructure some) && (scala_Some_apply startsWith "scala.Some.apply") => true
              case _ => false
            })) =>

            warn(tree, WrapNullWithOption)

          /// Comparing Option to None instead of using isDefined (disabled)
          /*case Apply(Select(opt, op), List(scala_None)) if (op == nme.EQ || op == nme.NE) && (scala_None is "scala.None") =>
            warn(tree, "Use .isDefined instead of comparing to None")*/

          /// filter(...).headOption is better written as find(...)
          case Select(Apply(Select(col, Name("filter")), List(Function(List(ValDef(_, _, _, _)), _))), Name("headOption")) =>

            warn(tree, UseFindNotFilterHead(identOrCol(col)))

          /// orElse(Some(...)).get is better written as getOrElse(...)
          case Select(Apply(TypeApply(Select(option, Name("orElse")), _), List(Apply(scala_Some_apply, List(_value)))), Name("get"))
            if scala_Some_apply startsWith "scala.Some.apply" =>

            warn(scala_Some_apply, UseGetOrElseOnOption(identOrOpt(option)))

          /// if (opt.isDefined) opt.get else something is better written as getOrElse(something) and similar warnings
          //TODO: improve the warning text, and curb the code duplication
          case If(Select(opt1, Name("isDefined")), getCase @ Select(opt2, Name("get")), elseCase) //duplication
            if (opt1 equalsStructure opt2) && !(elseCase.tpe.widen <:< NothingClass.tpe) =>

            if (elseCase equalsStructure Literal(Constant(null))) warn(opt2, UseOptionOrNull(identOrOpt(opt2), identOrOpt(opt2) + ".isDefined"))
            else if (getCase.tpe.widen <:< elseCase.tpe.widen)    warn(opt2, UseOptionGetOrElse(identOrOpt(opt2), identOrOpt(opt2) + ".isDefined"))

          case If(Select(Select(opt1, Name("isDefined")), nme.UNARY_!), elseCase, getCase @ Select(opt2, Name("get"))) //duplication
            if (opt1 equalsStructure opt2) && !(elseCase.tpe.widen <:< NothingClass.tpe) =>

            if (elseCase equalsStructure Literal(Constant(null))) warn(opt2, UseOptionOrNull(identOrOpt(opt2), "!" + identOrOpt(opt2) + ".isDefined"))
            else if (getCase.tpe.widen <:< elseCase.tpe.widen)    warn(opt2, UseOptionGetOrElse(identOrOpt(opt2), "!" + identOrOpt(opt2) + ".isDefined"))

          case If(Select(opt1, Name("isEmpty")), elseCase, getCase @ Select(opt2, Name("get"))) //duplication
            if (opt1 equalsStructure opt2) && !(elseCase.tpe.widen <:< NothingClass.tpe) =>

            if (elseCase equalsStructure Literal(Constant(null))) warn(opt2, UseOptionOrNull(identOrOpt(opt2), identOrOpt(opt2) + ".isEmpty"))
            else if (getCase.tpe.widen <:< elseCase.tpe.widen)    warn(opt2, UseOptionGetOrElse(identOrOpt(opt2), identOrOpt(opt2) + ".isEmpty"))

          case If(Select(Select(opt1, Name("isEmpty")), nme.UNARY_!), getCase @ Select(opt2, Name("get")), elseCase) //duplication
            if (opt1 equalsStructure opt2) && !(elseCase.tpe.widen <:< NothingClass.tpe) =>

            if (elseCase equalsStructure Literal(Constant(null))) warn(opt2, UseOptionOrNull(identOrOpt(opt2), "!" + identOrOpt(opt2) + ".isEmpty"))
            else if (getCase.tpe.widen <:< elseCase.tpe.widen)    warn(opt2, UseOptionGetOrElse(identOrOpt(opt2), "!" + identOrOpt(opt2) + ".isEmpty"))

          case If(Apply(Select(opt1, nme.NE), List(scala_None)), getCase @ Select(opt2, Name("get")), elseCase) //duplication
            if (scala_None is "scala.None") && (opt1 equalsStructure opt2) && !(elseCase.tpe.widen <:< NothingClass.tpe) =>

            if (elseCase equalsStructure Literal(Constant(null))) warn(opt2, UseOptionOrNull(identOrOpt(opt2), identOrOpt(opt2) + "!= None"))
            else if (getCase.tpe.widen <:< elseCase.tpe.widen)    warn(opt2, UseOptionGetOrElse(identOrOpt(opt2), identOrOpt(opt2) + "!= None"))

          case If(Apply(Select(opt1, nme.EQ), List(scala_None)), elseCase, getCase @ Select(opt2, Name("get"))) //duplication
            if (scala_None is "scala.None") && (opt1 equalsStructure opt2) && !(elseCase.tpe.widen <:< NothingClass.tpe) =>

            if (elseCase equalsStructure Literal(Constant(null))) warn(opt2, UseOptionOrNull(identOrOpt(opt2), identOrOpt(opt2) + "== None"))
            else if (getCase.tpe.widen <:< elseCase.tpe.widen)    warn(opt2, UseOptionGetOrElse(identOrOpt(opt2), identOrOpt(opt2) + "== None"))

          /// sortBy(...).head/last is better written as min/maxBy(...)
          case Select(Apply(Apply(TypeApply(Select(col, Name("sortBy")), _), List(_func)), List(ordering)), head_last)
            if (head_last.isAny("head", "last"))
            && (col.tpe.baseClasses.exists(_.tpe =:= TraversableOnceClass.tpe))
            && (ordering.contains("Ordering")) =>

            val func = head_last.toString
            val replacement = if (func == "head") "minBy" else "maxBy"
            warn(tree, UseMinOrMaxNotSort(identOrCol(col), "sortBy", func, replacement))

          case Select(Apply(xArrayOps, List(Apply(Apply(TypeApply(Select(col, Name("sortBy")), _), List(_func)), List(ordering)))), head_last)
            if (head_last.isAny("head", "last"))
            && (xArrayOps.containsAny("ArrayOps", "augmentString"))
            && (ordering.contains("Ordering")) =>

            val func = head_last.toString
            val replacement = if (func == "head") "minBy" else "maxBy"
            warn(tree, UseMinOrMaxNotSort(identOrCol(col), "sortBy", func, replacement))

          /// sorted(...).head/last is better written as min/max
          case Select(Apply(TypeApply(Select(col, Name("sorted")), _), List(ordering)), head_last)
            if (head_last.isAny("head", "last"))
            && (col.tpe.baseClasses.exists(_.tpe =:= TraversableOnceClass.tpe))
            && (ordering.contains("Ordering")) =>

            val func = head_last.toString
            val replacement = if (func == "head") "min" else "max"
            warn(tree, UseMinOrMaxNotSort(identOrCol(col), "sorted", func, replacement))

          case Select(Apply(xArrayOps, List(Apply(TypeApply(Select(col, Name("sorted")), _), List(ordering)))), head_last)
            if (head_last.isAny("head", "last"))
            && (xArrayOps.containsAny("ArrayOps", "augmentString"))
            && (ordering.contains("Ordering")) =>

            val func = head_last.toString
            val replacement = if (func == "head") "min" else "max"
            warn(tree, UseMinOrMaxNotSort(identOrCol(col), "sorted", func, replacement))

          case Select(_, _) if {

            // TODO Possibly a bug - tried with regular class too
            import scala.language.reflectiveCalls

            /// find(...).isDefined is better written as exists(...)
            case class FindIsDefined(col: String, isDefinedFunc: String, pos: Position) {
              def getReplacement(): (String, Boolean) = (
                  if (isDefinedFunc == "isEmpty") {
                    ("exists", true)
                  } else {
                    ("exists", false)
                  }
              )
            }

            for (findIsDefined <- Option(tree match {
              case Select(Apply(pos @ Select(col, Name("find")), _cond), isDefinedFunc)
                if isDefinedFunc.isAny("isEmpty", "nonEmpty", "isDefined")
                && col.tpe.baseClasses.exists(_.tpe =:= TraversableClass.tpe) =>

                FindIsDefined(identOrCol(col), isDefinedFunc.toString, pos.pos)

              case Select(Apply(xArrayOps, List(Apply(pos @ Select(col, Name("find")), List(_cond)))), isDefinedFunc)
                if isDefinedFunc.isAny("isEmpty", "nonEmpty", "isDefined")
                && xArrayOps.containsAny("ArrayOps", "augmentString") =>

                FindIsDefined(identOrCol(col), isDefinedFunc.toString, pos.pos)

              case Select(Apply(Select(Apply(xArrayOps, List(pos @ col)), Name("find")), List(_cond)), isDefinedFunc)
                if isDefinedFunc.isAny("isEmpty", "nonEmpty", "isDefined")
                && xArrayOps.containsAny("ArrayOps", "augmentString") =>

                FindIsDefined(identOrCol(col), isDefinedFunc.toString, pos.pos)

              case _ =>
                null
            })) {
              warn(findIsDefined.pos, UseExistsNotFindIsDefined(findIsDefined.col, findIsDefined.getReplacement, findIsDefined.isDefinedFunc))
            }

            ///[!]filter[Not](cond).[is|non]Empty -> [!]seq.[exists|forall]([!]cond) ... o_O
            // TODO parse cond for simple _ ==/!= x conditions to improve suggestions
            case class FilterEmpty(col: String, stmtNegated: Boolean, filterFunc: String, emptyFunc: String, pos: Position) {
              def getReplacements(): Seq[(String, Boolean, Boolean)] = (
                if (filterFunc == "filter") {
                  if (emptyFunc == "isEmpty" ^ stmtNegated) {
                    Seq(("exists", true, false), ("forall", false, true))
                  } else {
                    Seq(("exists", false, false))
                  }
                } else {
                  if (emptyFunc == "isEmpty" ^ stmtNegated) {
                    Seq(("forall", false, false))
                  } else {
                    Seq(("exists", false, true), ("forall", true, false))
                  }
                }
              )
            }

            def allowedFuncs(filterFunc: Name, emptyFunc: Name): Boolean = (
                 (filterFunc isAny ("filter", "filterNot"))
              && (emptyFunc isAny ("isEmpty", "nonEmpty", "isDefined"))
            )

            for (filterEmpty <- Option(tree match {
              case Select(Select(Apply(pos @ Select(col, filterFunc), _cond), emptyFunc), nme.UNARY_!)
                if allowedFuncs(filterFunc, emptyFunc)
                && col.tpe.baseClasses.exists(c => c.tpe =:= TraversableOnceClass.tpe || c.tpe =:= OptionClass.tpe) =>

                FilterEmpty(identOrCol(col), true, filterFunc.toString, emptyFunc.toString, pos.pos)

              case Select(Apply(pos @ Select(col, filterFunc), _cond), emptyFunc)
                if allowedFuncs(filterFunc, emptyFunc)
                && col.tpe.baseClasses.exists(c => c.tpe =:= TraversableOnceClass.tpe || c.tpe =:= OptionClass.tpe) =>

                FilterEmpty(identOrCol(col), false, filterFunc.toString, emptyFunc.toString, pos.pos)

              case Select(Apply(Select(Apply(Select(Apply(xArrayOps, List(pos @ col)), filterFunc), List(_cond)), emptyFunc), List()), nme.UNARY_!)
                if allowedFuncs(filterFunc, emptyFunc)
                && xArrayOps.containsAny("ArrayOps", "augmentString") =>

                FilterEmpty(identOrCol(col), false, filterFunc.toString, emptyFunc.toString, pos.pos)

              case Select(Apply(xArrayOps, List(Apply(pos @ Select(col, filterFunc), List(_cond)))), emptyFunc)
                if allowedFuncs(filterFunc, emptyFunc)
                && xArrayOps.containsAny("ArrayOps", "augmentString") =>

                FilterEmpty(identOrCol(col), false, filterFunc.toString, emptyFunc.toString, pos.pos)

              case Select(Apply(Select(Apply(xArrayOps, List(pos @ col)), filterFunc), List(_cond)), emptyFunc)
                if allowedFuncs(filterFunc, emptyFunc)
                && xArrayOps.containsAny("ArrayOps", "augmentString") =>

                FilterEmpty(identOrCol(col), false, filterFunc.toString, emptyFunc.toString, pos.pos)

              case _ =>
                null
            })) {
              warn(filterEmpty.pos, UseExistsNotFilterIsEmpty(filterEmpty.col, filterEmpty.getReplacements, filterEmpty.stmtNegated, filterEmpty.filterFunc, filterEmpty.emptyFunc))
              //Utils.noWarnPositions += filterEmpty.posHolder.pos // Prevents re-warning on negation
            }

            false
          } => //Fallthrough

          /// exists(a == ...) is better written as contains(...)
          case Apply(Select(col, Name("exists")), List(Function(List(ValDef(_, param1, _, _)), Apply(Select(param2, eq), List(id @ Ident(_))))))
            if (eq.isAny("$eq$eq", "eq"))
            && (col.tpe.baseClasses.exists(c => c.tpe =:= TraversableOnceClass.tpe || (c.tpe =:= OptionClass.tpe && !Properties.versionString.contains("2.10"))))
            && (param1.toString == param2.toString) =>

            warn(tree, UseContainsNotExistsEquals(identOrCol(col), id.toString, param2.toString, id.toString))

          case Apply(Select(col, Name("exists")), List(Function(List(ValDef(_, param1, _, _)), Apply(Select(id @ Ident(_), eq), List(param2)))))
            if (eq.isAny("$eq$eq", "eq"))
            && (col.tpe.baseClasses.exists(c => c.tpe =:= TraversableOnceClass.tpe || (c.tpe =:= OptionClass.tpe && !Properties.versionString.contains("2.10"))))
            && (param1.toString == param2.toString) =>

            warn(tree, UseContainsNotExistsEquals(identOrCol(col), id.toString, id.toString, param2.toString))

          case Apply(Select(col, Name("exists")), List(Function(List(ValDef(_, param1, _, _)), Apply(Select(param2, eq), List(Literal(Constant(lit)))))))
            if (eq.isAny("$eq$eq", "eq"))
            && (col.tpe.baseClasses.exists(c => c.tpe =:= TraversableOnceClass.tpe || (c.tpe =:= OptionClass.tpe && !Properties.versionString.contains("2.10"))))
            && (param1.toString == param2.toString) =>

            warn(tree, UseContainsNotExistsEquals(identOrCol(col), String.valueOf(lit.toString), param2.toString, String.valueOf(lit.toString)))


          /// fold(true)(acc && xxx) => forall
          /// fold(false)(acc || xxx) => exists
          //TODO: fix foldRight, reduceRight
          case Apply(Apply(TypeApply(Select(col, fold), List(_)), List(lit @ Literal(Constant(start)))), List(Function(List(ValDef(_, arg1, _, _), ValDef(_, arg2, _, _)), Apply(Select(arg1u, op), List(arg2u)))))
            if (fold.isAny("fold", "foldLeft", "$div$colon"))
            && (col.tpe.baseClasses.exists(_.tpe =:= TraversableOnceClass.tpe))
            && (((true == start || false == start)
                && ((arg1.toString == arg1u.toString) || (arg1.toString == arg2u.toString))
                && (op.isAny("$amp$amp", "$bar$bar")))
              || ((lit.tpe.widen weak_<:< DoubleClass.tpe)
                && (arg2u.tpe.widen =:= lit.tpe.widen)
                && ((arg1u.tpe.widen weak_<:< DoubleClass.tpe) || (arg2u.tpe.widen weak_<:< DoubleClass.tpe))
                && (Set(arg1.toString, arg2.toString) == Set(arg1u.toString, arg2u.toString))
                && ((op.isAny("$plus", "$times"))))) =>

            if (op.isAny("$amp$amp", "$bar$bar")) {
              if (start == true && (op is "$amp$amp")) {
                warn(tree, UseQuantifierFuncNotFold(identOrCol(col), "forall", fold.toString))
              } else if (start == false && (op is "$bar$bar")) {
                warn(tree, UseQuantifierFuncNotFold(identOrCol(col), "exists", fold.toString))
              } else {
                warn(tree, InvariantReturn(fold.toString, start.toString))
              }
            } else {
              if (op is "$plus") {
                val replacement = (if (start == 0) "sum" else "sum + " + start)
                warn(tree, UseFuncNotFold(identOrCol(col), replacement, fold.toString))
              } else if (op is "$times") {
                if (start == 0) {
                  warn(tree, InvariantReturn(fold.toString, start.toString))
                } else {
                  val replacement = (if (start == 1) "product" else "product * " + start)
                  warn(tree, UseFuncNotFold(identOrCol(col), replacement, fold.toString))
                }
              }
            }

          /// reduce(acc && xxx) => forall
          /// reduce(acc || xxx) => exists
          case Apply(TypeApply(Select(col, reduce), List(_)), List(Function(List(ValDef(_, arg1, _, _), ValDef(_, arg2, _, _)), Apply(Select(arg1u, op), List(arg2u)))))
            if (reduce.isAny("reduce", "reduceLeft"))
            && ((((arg1.toString == arg1u.toString) && (arg1u.tpe.widen <:< BooleanClass.tpe)
                ||(arg1.toString == arg2u.toString) && (arg2u.tpe.widen <:< BooleanClass.tpe))
                && (op.isAny("$amp$amp", "$bar$bar")))
              || (((arg1u.tpe.widen weak_<:< DoubleClass.tpe) || (arg2u.tpe.widen weak_<:< DoubleClass.tpe))
                && (Set(arg1.toString, arg2.toString) == Set(unwrapNumber(arg1u).toString, unwrapNumber(arg2u).toString))
                && (op.isAny("$plus", "$times", "min", "max")))) =>

            if (op is "$amp$amp") {
              warn(tree, UseQuantifierFuncNotFold(identOrCol(col), "forall", reduce.toString))
            } else if (op is "$bar$bar") {
              warn(tree, UseQuantifierFuncNotFold(identOrCol(col), "exists", reduce.toString))
            } else if (op is "$plus") {
              warn(tree, UseFuncNotReduce(identOrCol(col), "sum", reduce.toString))
            } else if (op is "$times") {
              warn(tree, UseFuncNotReduce(identOrCol(col), "product", reduce.toString))
            } else {
              warn(tree, UseFuncNotReduce(identOrCol(col), op.toString, reduce.toString))
            }

          /// col.map(...).map(...)
          // TODO too noisy
          case Apply(TypeApply(Select(Apply(Apply(TypeApply(Select(col, Name("map")), _), List(Function(List(ValDef(_, _, _, _)), _))), List(_canBuildFrom)), Name("map")), _), List(Function(List(ValDef(_, _, _, _)), _)))
            if (col.tpe.baseClasses.exists(_.tpe =:= TraversableClass.tpe)) =>

            warn(tree, MergeMaps)

          /// swap operations col.map(...).take(...)
          //TODO: head, last could be like that too
          case Select(Apply(Apply(TypeApply(Select(col, Name("map")), _), List(Function(List(ValDef(_, _, _, _)), _))), List(canBuildFrom)), func)
            if (col.tpe.baseClasses.exists(_.tpe =:= TraversableClass.tpe))
            && (func.isAny("take", "takeRight", "drop", "dropRight", "headOption", "lastOption", "init", "tail", "slice"))
            && (canBuildFrom.toString contains "canBuildFrom") =>

            warn(tree, FuncFirstThenMap(func.toString))

          case Select(Apply(Select(col, Name("map")), List(Function(List(ValDef(_, _, _, _)), _))), func)
            if (col.tpe.baseClasses.exists(_.tpe =:= TraversableClass.tpe))
            && (func.isAny("take", "takeRight", "drop", "dropRight", "headOption", "lastOption", "init", "tail", "slice")) =>

            warn(tree, FuncFirstThenMap(func.toString))

          /// swap operations col.sortWith(...).filter(...)
          case Apply(Select(Apply(Select(col, Name("sortWith")), List(Function(List(ValDef(_, _, _, _), ValDef(_, _, _, _)), _))), filter), List(Function(List(ValDef(_, _, _, _)), _)))
            if (col.tpe.baseClasses.exists(_.tpe =:= TraversableClass.tpe))
            && (filter.isAny("filter", "filterNot")) =>

            warn(tree, FilterFirstThenSort)

          /// swap operations col.sortBy(...).filter(...)
          case Apply(Select(Apply(Apply(TypeApply(Select(col, Name("sortBy")), _), List(Function(List(ValDef(_, _, _, _)), _))), List(ordering)), filter), List(Function(List(ValDef(_, _, _, _)), _)))
            if (col.tpe.baseClasses.exists(_.tpe =:= TraversableClass.tpe))
            && (ordering.contains("Ordering"))
            && (filter.isAny("filter", "filterNot")) =>

            warn(tree, FilterFirstThenSort)

          /// swap operations col.sorted.filter(...)
          case Apply(Select(Apply(TypeApply(Select(col, Name("sorted")), _), List(ordering)), filter), List(Function(List(ValDef(_, _, _, _)), _)))
            if (col.tpe.baseClasses.exists(_.tpe =:= TraversableClass.tpe))
            && (ordering.contains("Ordering"))
            && (filter.isAny("filter", "filterNot")) =>

            warn(tree, FilterFirstThenSort)

          /// flatMap(if (...) Seq(x) else Seq(y)) is better written as map(if (...) x else y)
          //TODO: actually check all returns of the lambda
          case Apply(TypeApply(Select(col, Name("flatMap")), _), List(Function(List(ValDef(_, _param, _, _)), If(_, Apply(TypeApply(Select(col1, Name("apply")), _), List(_elt1)), Apply(TypeApply(Select(col2, Name("apply")), _), List(_elt2))))))
            if (col.tpe.baseClasses.exists(_.tpe =:= TraversableClass.tpe))
            && (col1.tpe.baseClasses.exists(_.tpe =:= TraversableFactoryClass.tpe))
            && (col2.tpe.baseClasses.exists(_.tpe =:= TraversableFactoryClass.tpe)) =>

            warn(tree, UseMapNotFlatMap(identOrCol(col)))

          /// flatMap(if (...) x else Nil/None) is better written as filter(...)
          case Apply(TypeApply(Select(col, Name("flatMap")), _), List(Function(List(ValDef(_, param, _, _)), If(_, e1, e2)))) =>

            // Swap branches, to simplify the matching
            val (expr1, expr2) = if (e1 endsWithAny (".Nil", ".None")) (e1, e2) else (e2, e1)

            (expr1, expr2) match {
              case (nil, Apply(TypeApply(Select(col, Name("apply")), _), List(Ident(id)))) // <- col is shadowing
                if (col.toString matches "((scala[.])?collection[.])?immutable[.].*")
                && (nil endsWith ".Nil")
                && (id.toString == param.toString) =>

                warn(tree, UseFilterNotFlatMap(identOrCol(col)))

              case (Apply(_Option2Iterable1, List(none)), Apply(_Option2Iterable2, List(Apply(TypeApply(Select(some, Name("apply")), _), List(Ident(id))))))
                if (none is "scala.None")
                && (some is "scala.Some")
                && (id.toString == param.toString) =>

                warn(tree, UseFilterNotFlatMap(identOrCol(col)))

              case _ =>
                //println((showRaw(expr1), showRaw(expr2)))
            }

            /// Warning about using Option.{ headOption, lastOption, tail }
            case Select(Apply(_option2Iterable, List(opt)), bad)
              if (opt.tpe.widen.baseClasses.exists(_.tpe =:= OptionClass.tpe))
              && (bad.isAny("headOption", "lastOption", "tail")) =>

              warn(tree, AvoidOptionMethod(bad.toString))

            /// Warnings about using Option.size, which is probably a bug (use .isDefined instead)
            case t @ Select(Apply(option2Iterable, List(opt)), Name("size")) if (option2Iterable.toString contains "Option.option2Iterable") =>

              if (opt.tpe.widen.typeArgs.exists(tp => tp.widen <:< StringClass.tpe))
                warn(t, AvoidOptionStringSize)
              else if (opt.tpe.widen.typeArgs.exists(tp => tp.widen.baseClasses.exists(_.tpe =:= TraversableClass.tpe)))
                warn(t, AvoidOptionCollectionSize)
              else
                warn(t, AvoidOptionMethod("size", "use Option.isDefined instead."))

          /// Use x.transform instead of x = x.map(...)
          case Assign(id1, Apply(Apply(TypeApply(Select(Apply(xArrayOps, List(id2)), Name("map")), List(_, _)), List(_func)), List(Apply(TypeApply(canBuildFrom, List(_)), List(_typeTag)))))
            if (id1.toString == id2.toString)
            && (xArrayOps.contains("ArrayOps"))
            && (canBuildFrom.toString == "scala.this.Array.canBuildFrom") =>

            warn(tree, TransformNotMap("col")) //FIXME: name's inside xArrayOps, right?

          case Assign(id1, col @ Apply(Apply(TypeApply(Select(id2, Name("map")), List(_, _)), List(_func)), List(_)))
            if (id1.toString == id2.toString) && col.tpe.baseClasses.exists(_.tpe =:= MutableSeqLike.tpe) =>

            warn(tree, TransformNotMap(identOrCol(col)))

          /// Checks for duplicate mappings in a Map
          case Apply(TypeApply(Select(map, Name("apply")), _), args)
            if (map.tpe.baseClasses.exists(_.tpe <:< MapFactoryClass.tpe)) =>

            val keys = args flatMap {
              case Apply(TypeApply(Select(Apply(arrowAssoc, List(lhs)), arrow), _), _rhs)
                if (arrow.isAny("$minus$greater", "$u2192"))
                && (arrowAssoc.toString matches "scala[.](this[.])?Predef[.](any2)?ArrowAssoc.+")
                && (lhs.isInstanceOf[Literal] || lhs.isInstanceOf[Ident]) => //TODO: support pure functions someday
                List(lhs)

              case Apply(tuple2apply, List(lhs, _rhs))
                if (tuple2apply startsWith "scala.Tuple2.apply")
                && (lhs.isInstanceOf[Literal] || lhs.isInstanceOf[Ident]) =>
                List(lhs)

              case _k => //TODO: unknown mapping format
                Nil
            }

            val unitResult = keys.foldLeft(List.empty[Tree])((acc, newItem) =>
              if (acc.exists(item => item equalsStructure newItem)) {
                warn(newItem, DuplicateKeyInMap)
                acc
              } else {
                newItem :: acc
              }
            )

          //// Inefficient use of .size, instead of is/nonEmpty (Idea by non)
          /// Inefficient use of List.size, instead of is/nonEmpty
          case Apply(Select(pos @ Select(obj, size_length), op), List(Literal(Constant(x))))
            if (size_length isAny ("size", "length"))
            && (obj.tpe.widen.baseClasses.exists(_.tpe =:= ListClass.tpe) || obj.tpe.widen <:< StringClass.tpe)
            && (isEmptyCompare(x, op)) =>

            val replacementFunc = if (op == nme.EQ || op == nme.LE || op == nme.LT) "isEmpty" else "nonEmpty"

            warn(pos, InefficientUseOfListSize(identOrCol(obj), replacementFunc, size_length.toString))

          /// Inefficient use of String.size, instead of is/nonEmpty (disabled)
          /*case Apply(Select(obj, op), List(Literal(Constant(""))))
            if (obj.tpe.widen <:< StringClass.tpe)
            && (op == nme.EQ || op == nme.NE) =>

            if (op == nme.EQ)
              warn(tree, "Use isEmpty instead of comparing to empty string.")
            else
              warn(tree, "Use nonEmpty instead of comparing to empty string.")*/

          /// Passing a block that returns a function to map, instead of a function -- http://scalapuzzlers.com/#pzzlr-001
          case Apply(TypeApply(Select(collection, op), _), List(block @ Block(list, Function(List(ValDef(paramMods, _, _, _)), _funcBody))))
            if paramMods.isSynthetic
            && op.toString.matches("map|foreach|filter(Not)?") //TODO: add more
            && list.nonEmpty // col.map(func), where func is an already defined function
            && (list match { // eta expansion, similar to above
              case List(ValDef(_, eta, _, _)) if eta.startsWith("eta$") => false
              case _ => true
            })
            && collection.tpe.baseClasses.exists(_.tpe =:= TraversableClass.tpe) =>

            warn(block, OnceEvaluatedStatementsInBlockReturningFunction)

          /// Integer division in an expression assigned to a floating point variable
          case Assign(varName, body) if isFloatingPointType(varName) && hasIntegerDivision(body) =>
            warn(body, IntDivisionAssignedToFloat)
          case Apply(Select(_var, varSetter), List(body)) if (varSetter endsWith "_$eq") && isFloatingPointType(body) && hasIntegerDivision(body) =>
            warn(body, IntDivisionAssignedToFloat)
          case ValDef(_, _, tpe, body) if isFloatingPointType(tpe) && hasIntegerDivision(body) =>
            warn(body, IntDivisionAssignedToFloat)
          case Literal(Constant(_)) if (intLiteralDiv contains tree.pos) && isFloatingPointType(tree.tpe) =>
            warn(tree, IntDivisionAssignedToFloat)

          /// col.flatten instead of col.filter(_.isDefined).map(_.get)
          case Apply(TypeApply(Select(Apply(Select(col, filter), List(Function(List(ValDef(_, p1, _, _)), Select(p1_, isDefinedEmpty)))), Name("map")), _), List(Function(List(ValDef(_, p2, _, _)), Select(p2_, Name("get")))))
            if col.tpe.widen.baseClasses.exists(_.tpe =:= TraversableClass.tpe)
            && ((filter.is("filter") && isDefinedEmpty.isAny("isDefined", "nonEmpty"))
              ||(filter.is("filterNot") && isDefinedEmpty.isAny("isEmpty")))
            && (p1.toString == p1_.toString) && (p1_.tpe.widen.baseClasses.exists(_.tpe =:= OptionClass.tpe))
            && (p2.toString == p2_.toString) && (p2_.tpe.widen.baseClasses.exists(_.tpe =:= OptionClass.tpe)) =>

            warn(tree, UseFlattenNotFilterOption(identOrCol(col), filter.toString, isDefinedEmpty.toString))

          /// col.count(...) instead of col.filter(...).size/length (idea from pippi)
          case Select(Apply(Select(col, Name("filter")), List(_func)), size_length)
            if col.tpe.widen.baseClasses.exists(c => c.tpe =:= TraversableOnceClass.tpe || c.tpe =:= OptionClass.tpe)
            && size_length.isAny("size", "length") =>

            warn(tree, UseCountNotFilterLength(identOrCol(col), size_length.toString))

          case Select(Apply(xArrayOps, List(Apply(Select(col, Name("filter")), List(_func)))), size_length)
            if xArrayOps.containsAny("ArrayOps", "augmentString")
            && size_length.isAny("size", "length") =>

            warn(tree, UseCountNotFilterLength(identOrCol(col), size_length.toString))

          /// col.exists(...) instead of col.count(...) == 0 (or similar)
          case Apply(Select(Apply(Select(col, Name("count")), List(_func)), op), List(Literal(Constant(x))))
            if col.tpe.widen.baseClasses.exists(c => c.tpe =:= TraversableOnceClass.tpe) && isEmptyCompare(x, op)
            && !(op == nme.EQ || op == nme.LE || op == nme.LT) =>

            warn(tree, UseExistsNotCountCompare(identOrCol(col)))

          case Select(Select(Select(col, Name("reverse")), Name("tail")), Name("reverse"))
            if col.tpe.widen.baseClasses.exists(c => c.tpe =:= TraversableClass.tpe) =>

            warn(tree, UseInitNotReverseTailReverse(identOrCol(col)))

          case Select(Apply(xArrayOps0, List(Select(Apply(xArrayOps1, List(Select(Apply(xArrayOps2, List(col)), Name("reverse")))), Name("tail")))), Name("reverse"))
            if (xArrayOps0.containsAny("ArrayOps", "augmentString"))
            && (xArrayOps1.containsAny("ArrayOps", "augmentString"))
            && (xArrayOps2.containsAny("ArrayOps", "augmentString")) =>

            warn(tree, UseInitNotReverseTailReverse(identOrCol(col)))

          case Select(Apply(Select(Select(col, Name("reverse")), Name("take")), _), Name("reverse"))
            if col.tpe.widen.baseClasses.exists(c => c.tpe =:= TraversableClass.tpe) =>

            warn(tree, UseTakeRightNotReverseTakeReverse(identOrCol(col)))

          case Select(Apply(xArrayOps0, List(Apply(Select(Apply(xArrayOps1, List(Select(Apply(xArrayOps2, List(col)), Name("reverse")))), Name("take")), _))), Name("reverse"))
            if (xArrayOps0.containsAny("ArrayOps", "augmentString"))
            && (xArrayOps1.containsAny("ArrayOps", "augmentString"))
            && (xArrayOps2.containsAny("ArrayOps", "augmentString")) =>

            warn(tree, UseTakeRightNotReverseTakeReverse(identOrCol(col)))

          case Select(Select(col, Name("reverse")), head)
            if col.tpe.widen.baseClasses.exists(c => c.tpe =:= TraversableClass.tpe)
            && (head.isAny("head", "headOption")) =>

            warn(tree, UseLastNotReverseHead(identOrCol(col), head is "headOption"))

          case Select(Apply(xArrayOps1, List(Select(Apply(xArrayOps2, List(col)), Name("reverse")))), head)
            if (xArrayOps1.containsAny("ArrayOps", "augmentString"))
            && (xArrayOps2.containsAny("ArrayOps", "augmentString"))
            && (head.isAny("head", "headOption")) =>

            warn(tree, UseLastNotReverseHead(identOrCol(col), head is "headOption"))

          case Select(Select(col, Name("reverse")), func)
            if col.tpe.widen.baseClasses.exists(c => c.tpe =:= TraversableClass.tpe)
            && (func.isAny("map", "iterator")) =>

            warn(tree, UseFuncNotReverse(identOrCol(col), func.toString))

          case Select(Apply(xArrayOps1, List(Select(Apply(xArrayOps2, List(col)), Name("reverse")))), func)
            if (xArrayOps1.containsAny("ArrayOps", "augmentString"))
            && (xArrayOps2.containsAny("ArrayOps", "augmentString"))
            && (func.isAny("map", "iterator")) =>

            warn(tree, UseFuncNotReverse(identOrCol(col), func.toString))

          case Apply(Select(col, Name("apply")), List(Literal(Constant(0))))
            if col.tpe.widen.baseClasses.exists(c => c.tpe =:= ListClass.tpe) =>

            warn(tree, UseHeadNotApply(identOrCol(col)))

          case Apply(Select(col, Name("apply")), List(Apply(Select(Select(col1, size_length), Name("$minus")), List(Literal(Constant(1))))))
            if col.tpe.widen.baseClasses.exists(c => c.tpe =:= ListClass.tpe)
            && size_length.isAny("size", "length")
            && (col equalsStructure col1) =>

            warn(tree, UseLastNotApply(identOrCol(col)))

          // if (list.nonEmpty) Some(list.head) else None
          case If(Select(col, Name("nonEmpty")), Apply(TypeApply(Select(scala_Some, Name("apply")), _), List(Select(col1, head_last))), scala_None)
            if (col equalsStructure col1)
            && head_last.isAny("head", "last")
            && (scala_Some is "scala.Some")
            && (scala_None is "scala.None") =>

            head_last match {
              case Name("head") => warn(tree, UseHeadOptionNotIf(identOrCol(col)))
              case Name("last") => warn(tree, UseLastOptionNotIf(identOrCol(col)))
            }


          // if (str.nonEmpty) Some(str.head) else None
          case If(Select(Apply(xArrayOps1, List(col)), Name("nonEmpty")), Apply(TypeApply(Select(scala_Some, Name("apply")), _), List(Select(Apply(xArrayOps2, List(col1)), head_last))), scala_None)
            if (col equalsStructure col1)
            && head_last.isAny("head", "last")
            && (xArrayOps1.containsAny("ArrayOps", "augmentString"))
            && (xArrayOps2.containsAny("ArrayOps", "augmentString"))
            && (scala_Some is "scala.Some")
            && (scala_None is "scala.None") =>

            head_last match {
              case Name("head") => warn(tree, UseHeadOptionNotIf(identOrCol(col)))
              case Name("last") => warn(tree, UseLastOptionNotIf(identOrCol(col)))
            }

          // if (list.isEmpty) None else Some(list.head)
          case If(Select(col, Name("isEmpty")), scala_None, Apply(TypeApply(Select(scala_Some, Name("apply")), _), List(Select(col1, head_last))))
            if (col equalsStructure col1)
            && head_last.isAny("head", "last")
            && (scala_Some is "scala.Some")
            && (scala_None is "scala.None") =>

            head_last match {
              case Name("head") => warn(tree, UseHeadOptionNotIf(identOrCol(col)))
              case Name("last") => warn(tree, UseLastOptionNotIf(identOrCol(col)))
            }

          // if (str.isEmpty) None else Some(s.head)
          case If(Apply(Select(col, Name("isEmpty")), _), scala_None, Apply(TypeApply(Select(scala_Some, Name("apply")), _), List(Select(Apply(xArrayOps, List(col1)), head_last))))
            if (col equalsStructure col1)
            && head_last.isAny("head", "last")
            && (xArrayOps.containsAny("ArrayOps", "augmentString"))
            && (scala_Some is "scala.Some")
            && (scala_None is "scala.None") =>

            head_last match {
              case Name("head") => warn(tree, UseHeadOptionNotIf(identOrCol(col)))
              case Name("last") => warn(tree, UseLastOptionNotIf(identOrCol(col)))
            }

          // if (!list.isEmpty) Some(list.head) else None
          case If(Select(Select(col, Name("isEmpty")), nme.UNARY_!), Apply(TypeApply(Select(scala_Some, Name("apply")), _), List(Select(col1, head_last))), scala_None)
            if (col equalsStructure col1)
            && head_last.isAny("head", "last")
            && (scala_Some is "scala.Some")
            && (scala_None is "scala.None") =>

            head_last match {
              case Name("head") => warn(tree, UseHeadOptionNotIf(identOrCol(col)))
              case Name("last") => warn(tree, UseLastOptionNotIf(identOrCol(col)))
            }

          // if (!str.isEmpty) Some(str.head) else None
          case If(Select(Apply(Select(col, Name("isEmpty")), _), nme.UNARY_!), Apply(TypeApply(Select(scala_Some, Name("apply")), _), List(Select(Apply(xArrayOps, List(col1)), head_last))), scala_None)
            if (col equalsStructure col1)
            && head_last.isAny("head", "last")
            && (xArrayOps.containsAny("ArrayOps", "augmentString"))
            && (scala_Some is "scala.Some")
            && (scala_None is "scala.None") =>

            head_last match {
              case Name("head") => warn(tree, UseHeadOptionNotIf(identOrCol(col)))
              case Name("last") => warn(tree, UseLastOptionNotIf(identOrCol(col)))
            }

          case Apply(TypeApply(Select(col, Name("zip")), _), List(Select(col1, Name("indices"))))
            if (col equalsStructure col1)
            && col.tpe.widen.baseClasses.exists(c => c.tpe =:= TraversableClass.tpe) =>

            warn(tree, UseZipWithIndexNotZipIndices(identOrCol(col)))

          case Apply(TypeApply(Select(Apply(xArrayOps1, List(col)), Name("zip")), _), List(Select(Apply(xArrayOps2, List(col1)), Name("indices"))))
            if (col equalsStructure col1)
            && (xArrayOps1.containsAny("ArrayOps", "augmentString"))
            && (xArrayOps2.containsAny("ArrayOps", "augmentString")) =>

            warn(tree, UseZipWithIndexNotZipIndices(identOrCol(col)))

          /// Use partial function directly - temporary variable is unnecessary (idea by yzgw)
          case Apply(_, List(Function(List(ValDef(mods, x_1, typeTree: TypeTree, EmptyTree)), Match(x_1_, _))))
            if (((x_1 is "x$1") && (x_1_ is "x$1") && (mods.isSynthetic) && (mods.isParameter)) // _ match { ... }
            ||  ((x_1.toString == x_1_.toString) && !(mods.isSynthetic) && (mods.isParameter))) // x => x match { ... }
            && (typeTree.original == null) => // Fails on: x: Type => x match { ... }

            val param = if (x_1 is "x$1") "_" else x_1.toString + " => " + x_1.toString

            //TODO: also detects for (x <- col) x match { ... } ... current workaround with filter has false negatives
            val line = tree.pos.lineContent
            if (!List("for", "<-").exists(line.contains(_))) {
              warn(tree, PassPartialFunctionDirectly(param))
            }

          /// Using the implicit ordering for Unit is probably wrong
          case Apply(Apply(TypeApply(Select(_, minMaxBy), _), List(Function(_, Block(_, u @ Literal(Constant(())))))), List(Select(scala_math_Ordering, unit)))
            if (minMaxBy.isAny("minBy", "maxBy"))
            && (scala_math_Ordering is staticSelect("math", "Ordering"))
            && (unit is "Unit") =>

            warn(u, UnitImplicitOrdering(minMaxBy.toString))

          case _ =>
            //if (tree.toString contains "...") println(showRaw(tree))
        }
      } catch catcher finally finalizer(tree)
    }
  }

  //// Abstract Interpreter
  private[this] object PostTyperInterpreterComponent extends PluginComponent {
    val global = LinterPlugin.this.global
    import global._

    override val runsAfter = List("typer")

    val phaseName = "linter-typed-interpreter"

    override def newPhase(prev: Phase): StdPhase = new StdPhase(prev) {
      override def apply(unit: global.CompilationUnit): Unit = {
        if (!unit.isJava) {
          nowarnPositions.clear
          new PostTyperInterpreterTraverser(unit).traverse(unit.body)
        }
      }
    }

    class PostTyperInterpreterTraverser(unit: CompilationUnit) extends Traverser {
      implicit val unitt: CompilationUnit = unit
      var treePosHolder: Tree = null
      import org.psywerx.hairyfotr.Utils._
      val utils = new Utils[global.type](global)
      import utils._

      import definitions.{ AnyClass, AnyValClass, NothingClass, PredefModule, ObjectClass, Object_== }
      import definitions.{ OptionClass, SeqClass, TraversableClass, ListClass, StringClass }
      import definitions.{ DoubleClass, FloatClass, CharClass, ByteClass, ShortClass, IntClass, LongClass, BooleanClass }

      def checkRegex(reg: String): Unit = {
        try {
          val regex = reg.r
          if (reg matches "[\\^]?([|]+|([(][|]*[)])+)+[$]?") warn(treePosHolder, RegexWarning("Regex "+reg+" matches only empty string", error = false))
        } catch {
          case e: java.util.regex.PatternSyntaxException =>
            warn(treePosHolder, RegexWarning(e.getDescription))
          case e: Exception =>
        }
      }

      // Data structure ideas:
      // 1. common trait for Value, Collection, StringAttrs
      // 2. for Values: empty -> any, and add "nonValues" or "conditions" to cover
      //    if (a == 0) ..., if (a%2 == 0) ... even for huge collections

      //// Integer/List value data structure
      object Values {
        lazy val empty = new Values()
        def apply(i: Int, name: String = ""): Values = new Values(name = name, values = Set(i))
        def apply(low: Int, high: Int): Values = new Values(ranges = Set((low, high)))
      }
      class Values(
          val ranges: Set[(Int, Int)] = Set.empty,
          val values: Set[Int] = Set.empty,
          val conditions: Set[Int => Boolean] = Set.empty, // Currently obeyed only ifEmpty
          val name: String = "",
          val isSeq: Boolean = false,
          val actualSize: Int = -1
        ) {

        //require(isEmpty || isValue || isSeq || (ranges.size == 1 && values.size == 0) || (ranges.size == 0 && values.size > 0))
        //println(this)

        def conditionsContain(i: Int): Boolean = conditions.forall(c => c(i))

        // Experimental alternative to empty
        def makeUnknown: Values = new Values(conditions = conditions)

        def rangesContain(i: Int): Boolean = (ranges exists { case (low, high) => i >= low && i <= high })

        def notSeq: Values = new Values(ranges, values, conditions, name, false)

        def contains(i: Int): Boolean = (values contains i) || rangesContain(i)
        def containsAny(i: Int*): Boolean = i exists { i => this.contains(i) }
        def apply(i: Int): Boolean = contains(i)
        //TODO: this crashes if (high-low) > Int.MaxValue - code manually, or break large ranges into several parts
        //def exists(func: Int => Boolean) = (values exists func) || (ranges exists { case (low, high) => (low to high) exists func })
        //def forall(func: Int => Boolean) = (values forall func) && (ranges forall { case (low, high) => (low to high) forall func })
        def existsLower(i: Int): Boolean = (values exists { _ < i }) || (ranges exists { case (low, high) => low < i })
        def existsGreater(i: Int): Boolean = (values exists { _ > i }) || (ranges exists { case (low, high) => high > i })
        def forallLower(i: Int): Boolean = (values forall { _ < i }) && (ranges forall { case (low, high) => (low <= high) && (high < i) })
        def forallEquals(i: Int): Boolean = (values forall { _ == i }) && (ranges forall { case (low, high) => (low == high) && (i == low) })

        def addRange(low: Int, high: Int): Values = new Values(ranges + (if (low > high) (high, low) else (low, high)), values, conditions, name, false, -1)
        def addValue(i: Int): Values = new Values(ranges, values + i, conditions, name, false, -1)
        def addSet(s: Set[Int]): Values = new Values(ranges, values ++ s, conditions, name, false, -1) //TODO: are these false, -1 ok?
        def addCondition(c: Int => Boolean): Values = new Values(ranges, values, conditions + c, name, isSeq, actualSize)
        def addConditions(c: (Int => Boolean)*): Values = new Values(ranges, values, conditions ++ c, name, isSeq, actualSize)
        def addConditions(c: Set[Int => Boolean]): Values = new Values(ranges, values, conditions ++ c, name, isSeq, actualSize)
        def addName(s: String): Values = new Values(ranges, values, conditions, s, isSeq, actualSize)
        def addActualSize(s: Int): Values = new Values(ranges, values, conditions, name, isSeq, s)
        //TODO: this can go wrong in many ways - (low to high) limit, freeze on huge collection, etc

        def distinct: Values = {
          val t = this.optimizeValues
          //ADD: optimizeranges that makes them non-overlapping
          if (t.values.size == 0 && t.ranges.size == 1) {
            new Values(ranges = t.ranges, conditions = conditions, /*name = name,*/ isSeq = isSeq, actualSize = actualSize)
          } else {
            new Values(values = t.values ++ t.ranges.flatMap { case (low, high) => (low to high) }, conditions = conditions, /*name = name,*/ isSeq = isSeq, actualSize = actualSize)
          }
        }
        //TODO: Is this correct for weird code?
        def sum: Int = {
          val t = this.distinct
          t.values.sum + ranges.foldLeft(0)((acc, n) => acc + (n._1 to n._2).sum)
        }

        // Discard values, that are inside ranges
        def optimizeValues: Values = new Values(ranges, values.filter(v => !rangesContain(v)), conditions, name, isSeq, actualSize)
        //TODO: def optimizeRanges =

        def isEmpty: Boolean = this.size == 0
        def nonEmpty: Boolean = this.size > 0
        def isValue: Boolean = this.values.size == 1 && this.ranges.isEmpty //TODO: this is stupid and buggy, and woudn't exist if I didn't mix all types into one class
        def getValue: Int = if (isValue) values.head else throw new Exception()
        def isValueForce: Boolean = (this.values.size == 1 && this.ranges.isEmpty) || (ranges.size == 1 && this.size == 1)
        def getValueForce: Int = if (isValue) values.head else if (ranges.size == 1 && this.size == 1) ranges.head._1 else throw new Exception()

        def max: Int = math.max(if (values.nonEmpty) values.max else Int.MinValue, if (ranges.nonEmpty) ranges.maxBy(_._2)._2 else Int.MinValue)
        def min: Int = math.min(if (values.nonEmpty) values.min else Int.MaxValue, if (ranges.nonEmpty) ranges.minBy(_._1)._1 else Int.MaxValue)

        def dropValue(i: Int): Values = new Values(
          ranges.flatMap { case (low, high) =>
            if (i > low && i < high) List((low, i-1), (i+1, high)) else
            if (i == low && i < high) List((low+1, high)) else
            if (i > low && i == high) List((low, high-1)) else
            if (i == low && i == high) Nil else
            List((low, high))
          },
          values - i,
          Set.empty, //ADD: conditions?
          name)

        // Beware of some operations over ranges.
        def map(func: Int => Int, rangeSafe: Boolean = true): Values = //ADD: conditions?
          if (rangeSafe) {
            new Values(
              ranges.map { case (preLow, preHigh) =>
                  val (low, high) = (func(preLow), func(preHigh))
                  if (low > high) (high, low) else (low, high)
              },
              values.map(func))
          } else {
            if (this.ranges.nonEmpty && this.size <= 100001) //ADD: this only checks small ranges, also can still get slow
              (new Values(values = values ++ ranges.flatMap { case (low, high) => (low to high) }, name = name, isSeq = isSeq, actualSize = actualSize)).map(func)
            else
              Values.empty
          }

        //// Apply and check a condition, return a pair of possible values of this Integer/List if condition is true and if false
        def applyCond(condExpr: Tree): (Values, Values) = {
          //TODO: alwaysTrue and false cound be possible returns for error msgs
          //TODO: check out if you're using 'this' for things that aren't... this method shouldn't be here anyways
          //println("expr: "+showRaw(condExpr))
          var alwaysHold, neverHold = false
          val out = condExpr match {
            case Apply(Select(expr1, nme.ZAND), List(expr2)) => //&&
              this.applyCond(expr1)._1.applyCond(expr2)._1

            case Apply(Select(expr1, nme.ZOR), List(expr2)) => //||
              val (left, right) = (this.applyCond(expr1)._1, this.applyCond(expr2)._1)
              new Values(
                left.ranges ++ right.ranges,
                left.values ++ right.values,
                left.conditions ++ right.conditions,
                this.name)

            // Pass on String functions
            case strFunc @ Apply(Select(string, _func), _params)
              if string.tpe != null && string.tpe.widen <:< StringClass.tpe =>

              computeExpr(strFunc)

            case strFunc @ Select(Apply(scala_augmentString, List(_string)), _func)
              if (scala_augmentString.toString endsWith "augmentString") =>

              computeExpr(strFunc)

            //ADD: expr op expr that handles idents right
            case Apply(Select(Ident(v), op), List(expr)) if v.toString == this.name && computeExpr(expr).isValue =>

              val value = computeExpr(expr).getValue
              val out: Values = op match {
                case nme.EQ =>
                  (if (this.nonEmpty) {
                    if (this.contains(value)) {
                      if (this.forallEquals(value)) alwaysHold = true
                      Values(value).addName(name)
                    } else {
                      neverHold = true
                      this.makeUnknown
                    }
                  } else if (!this.conditionsContain(value)) {
                    neverHold = true
                    this.makeUnknown
                  } else {
                    this.makeUnknown
                  }).addCondition(_ == value)
                case nme.NE =>
                  (if (this.contains(value)) {
                    val out = this.dropValue(value)
                    if (out.isEmpty) neverHold = true
                    out
                  } else if (this.nonEmpty) {
                    alwaysHold = true
                    this
                  } else {
                    this.makeUnknown
                  }).addCondition(_ != value)
                case nme.GT => new Values(
                    ranges.flatMap { case (low, high) => if (low > value) Some((low, high)) else if (high > value) Some((value+1, high)) else None },
                    values.filter { _ > value },
                    Set(_ > value),
                    this.name)
                case nme.GE => new Values(
                    ranges.flatMap { case (low, high) => if (low >= value) Some((low, high)) else if (high >= value) Some((value, high)) else None },
                    values.filter { _ >= value },
                    Set(_ >= value),
                    this.name)
                case nme.LT => new Values(
                    ranges.flatMap { case (low, high) => if (high < value) Some((low, high)) else if (low < value) Some((low, value-1)) else None },
                    values.filter { _ < value },
                    Set(_ < value),
                    this.name)
                case nme.LE => new Values(
                    ranges.flatMap { case (low, high) => if (high <= value) Some((low, high)) else if (low <= value) Some((low, value)) else None },
                    values.filter { _ <= value },
                    Set(_ <= value),
                    this.name)
                case _ =>
                  //println("applyCond: "+showRaw( a ));
                  this.makeUnknown
              }

              if ((Set[Name](nme.GT, nme.GE, nme.LT, nme.LE) contains op) && this.nonEmpty) {
                if (out.isEmpty) neverHold = true
                if (out.size == this.size) alwaysHold = true
              }

              out
            case Apply(Select(expr1, op), List(expr2)) =>
              val (left, right) = (computeExpr(expr1), computeExpr(expr2))
              var out = Values.empty.addActualSize(this.actualSize)
              val startOut = out
              op match {
                case nme.EQ | nme.NE =>
                  if (left.isValue && right.isValue && left.getValue == right.getValue) {
                    if (op == nme.EQ) alwaysHold = true else neverHold = true
                    if (op == nme.EQ && (left.name == this.name || right.name == this.name)) out = this
                  } else if (left.nonEmpty && right.nonEmpty && (left.min > right.max || right.min > left.max)) {
                    if (op != nme.EQ) alwaysHold = true else neverHold = true
                    if (op != nme.EQ && (left.name == this.name || right.name == this.name)) out = this
                  } else if (left.name == this.name && right.isValueForce && op == nme.EQ) {
                    out = Values(right.getValueForce).addName(this.name)
                  } else if (right.name == this.name && left.isValueForce && op == nme.EQ) { // Yoda conditions
                    out = Values(left.getValueForce).addName(this.name)
                  }
                  if (left.isEmpty && left.conditions.nonEmpty && right.isValue && !left.conditionsContain(right.getValue)) {
                    neverHold = true
                  }

                case nme.GT | nme.LE =>
                  if (left.isValue && right.isValue) {
                    if (left.getValue > right.getValue) {
                      if (op == nme.GT) alwaysHold = true else neverHold = true
                      if (op == nme.GT && (left.name == this.name || right.name == this.name)) out = this
                    } else {
                      if (op != nme.GT) alwaysHold = true else neverHold = true
                      if (op != nme.GT && (left.name == this.name || right.name == this.name)) out = this
                    }
                  } else if (left.nonEmpty && right.nonEmpty) {
                    if (left.max <= right.min) {
                      if (op != nme.GT) alwaysHold = true else neverHold = true
                      if (op != nme.GT && (left.name == this.name || right.name == this.name)) out = this
                    } else if (left.min > right.max) {
                      if (op == nme.GT) alwaysHold = true else neverHold = true
                      if (op == nme.GT && (left.name == this.name || right.name == this.name)) out = this
                    }
                  }

                case nme.GE | nme.LT =>
                  if (left.isValue && right.isValue) {
                    if (left.getValue >= right.getValue) {
                      if (op == nme.GE) alwaysHold = true else neverHold = true
                      if (op == nme.GE && (left.name == this.name || right.name == this.name)) out = this
                    } else {
                      if (op != nme.GE) alwaysHold = true else neverHold = true
                      if (op != nme.GE && (left.name == this.name || right.name == this.name)) out = this
                    }
                  } else if (left.nonEmpty && right.nonEmpty) {
                    if (left.max < right.min) {
                      if (op != nme.GE) alwaysHold = true else neverHold = true
                      if (op != nme.GE && (left.name == this.name || right.name == this.name)) out = this
                    } else if (left.min >= right.max) {
                      if (op == nme.GE) alwaysHold = true else neverHold = true
                      if (op == nme.GE && (left.name == this.name || right.name == this.name)) out = this
                    }
                  }

                case _ =>
              }

              if (this.name == out.name) out.addConditions(this.conditions) else out
            case Select(expr, op) =>
              computeExpr(expr).applyUnary(op)
              Values.empty.addActualSize(this.actualSize)

            case expr =>
              //println("expr: "+showRaw(expr))
              computeExpr(expr)
              Values.empty.addActualSize(this.actualSize)
          }


          // Check if condition will always or never hold
          if (neverHold) warn(condExpr, InvariantCondition(always = false, "hold"))
          if (alwaysHold) warn(condExpr, InvariantCondition(always = true, "hold"))

          //TODO: verify filter = ...
          if (!isUsed(condExpr, this.name, filter = "size|length|head|last")) (this, this) else (out, if (neverHold) this else if (alwaysHold) Values.empty else this - out)
        }

        //TODO: does this even work? the v map is suspect and ugly
        def -(value: Values): Values = {
          if (this.isEmpty || value.isEmpty) {
            Values.empty
          } else {
            var out = this
            value map({ v => out = out.dropValue(v); v }, rangeSafe = false)
            out
          }
        }

        //// Apply a unary operation on a Integer/List variable
        def applyUnary(op: Name): Values = op match {
          case nme.UNARY_+ => this
          case nme.UNARY_- => this.map(a => -a)
          case nme.UNARY_~ => this.map(a => ~a, rangeSafe = false)
          case signum if signum.toString == "signum" => this.map(a => math.signum(a)).addCondition({ a => Set(-1, 0, +1) contains a })
          case abs if abs.toString == "abs" =>
            new Values(//ADD: conditions
              ranges.map { case (preLow, preHigh) =>
                val (low, high) = (if (preLow > 0) preLow else 0, math.max(math.abs(preLow), math.abs(preHigh)))
                if (low > high) (high, low) else (low, high)
              },
              values.map(a => math.abs(a))).addCondition(_ >= 0)

          case size if (size.toString matches "size|length") => if (this.actualSize != -1) Values(this.actualSize) else Values.empty.addCondition(_ >= 0)
          case head_last if (head_last.toString matches "head|last") =>
            // Only works for one element :)
            if (this.actualSize == 0) warn(treePosHolder, DecomposingEmptyCollection(head_last.toString))
            if (this.actualSize == 1 && this.size == 1) Values(this.getValueForce) else Values.empty
          case tail_init if (tail_init.toString matches "tail|init") && this.actualSize != -1 =>
            if (this.actualSize == 0) {
              warn(treePosHolder, DecomposingEmptyCollection(tail_init.toString))
              Values.empty
            } else {
              Values.empty.addActualSize(this.actualSize - 1)
            }

          case to if (to.toString matches "toIndexedSeq|toList|toSeq|toVector") => this //only immutable
          case distinct if (distinct.toString == "distinct") && this.actualSize != -1 =>
            val out = this.distinct
            out.addActualSize(out.size)

          case id if (id.toString == "reverse") => this // Will hold, while Set is used for values
          case max if (max.toString == "max") && this.nonEmpty => Values(this.max)
          case min if (min.toString == "min") && this.nonEmpty => Values(this.min)
          case sum if (sum.toString == "sum") && this.nonEmpty && this.distinct.size == this.actualSize => Values(this.sum)

          case empty if (empty.toString == "isEmpty") && (this.actualSize != -1) =>
            warn(treePosHolder, InvariantCondition(always = this.actualSize == 0, "hold"))
            Values.empty
          case empty if (empty.toString == "nonEmpty") && (this.actualSize != -1) =>
            warn(treePosHolder, InvariantCondition(always = this.actualSize > 0, "hold"))
            Values.empty

          case _ =>
            //val raw = showRaw( treePosHolder );
            //println("applyUnary: op:"+op+" thisval:"+this+" tree:"+treePosHolder.toString+"\n"+raw);
            Values.empty
        }
        //// Apply a binary operation on a Integer/List variable
        def applyBinary(op: Name, right: Values): Values = {
          val left = this

          val (func, isRangeSafe): ((Int, Int) => Int, Boolean) =
            op match {
              case nme.ADD => (_ + _, true)
              case nme.SUB => (_ - _, true)
              case nme.MUL => (_ * _, false) //ADD: are all these really unsafe for ranges (where you only process first and last)
              case nme.AND => (_ & _, false)
              case nme.OR  => (_ | _, false)
              case nme.XOR => (_ ^ _, false)
              case nme.LSL => (_ << _, false)
              case nme.LSR => (_ >>> _, false)
              case nme.ASR => (_ >> _, false)
              case nme.MOD if right.size == 1 && right.getValueForce != 0 => (_ % _, false)
              case nme.DIV if right.size == 1 && right.getValueForce != 0 => (_ / _, false)
              case a if a.toString matches "apply|take|drop|max|min|contains|map|count" => ((a: Int, b: Int) => throw new Exception(), false) // Check code below
              case _ => return Values.empty
           }

          val out: Values = (
            if (op.toString == "count") {
              (if (left.actualSize == 0) Values(0) else if (left.actualSize > 0) Values.empty.addCondition(_ < left.actualSize) else Values.empty).addCondition(_ >= 0)
            } else if (left.isEmpty || right.isEmpty) {
              //ADD: x & 2^n is Set(2^n, 0) and stuff like that :)
              if (left.contains(0) || right.contains(0)) {
                if (Set[Name](nme.MUL, nme.AND) contains op) Values(0) else Values.empty
              } else {
                Values.empty
              }
            } else if (op.toString == "apply") {
              //TODO: if you wanted actual values, you need to save seq type and refactor values from Set to Seq
              if (left.isSeq && left.actualSize == 1 && left.size == 1 && right.isValueForce && right.getValueForce == 0) Values(left.getValueForce) else left
            } else if (op.toString == "map") {
              right
            } else if (op.toString == "contains") {
              if (right.isValue) {
                warn(treePosHolder, InvariantCondition(always = left.contains(right.getValue), "return true"))
              }
              Values.empty
            } else if (op.toString == "max") {
              if (left.isValue && right.isValue) {
                if (left.getValue >= right.getValue) {
                  warn(treePosHolder, InvariantExtrema(max = true, returnsFirst = true))
                } else {
                  warn(treePosHolder, InvariantExtrema(max = true, returnsFirst = false))
                }
                Values(math.max(left.getValue, right.getValue))
              } else if (left.isValue && !right.isValue) {
                if (left.getValue >= right.max) {
                  warn(treePosHolder, InvariantExtrema(max = true, returnsFirst = true))
                  Values(left.getValue)
                } else if (left.getValue <= right.min) {
                  warn(treePosHolder, InvariantExtrema(max = true, returnsFirst = false))
                  right
                } else {
                  Values.empty
                }
              } else if (!left.isValue && right.isValue) {
                if (right.getValue >= left.max) {
                  warn(treePosHolder, InvariantExtrema(max = true, returnsFirst = false))
                  Values(right.getValue)
                } else if (right.getValue <= left.min) {
                  warn(treePosHolder, InvariantExtrema(max = true, returnsFirst = true))
                  left
                } else {
                  Values.empty
                }
              } else {
                Values.empty
              }
            } else if (op.toString == "min") {
              if (left.isValue && right.isValue) {
                if (left.getValue <= right.getValue) {
                  warn(treePosHolder, InvariantExtrema(max = false, returnsFirst = true))
                } else {
                  warn(treePosHolder, InvariantExtrema(max = false, returnsFirst = false))
                }
                Values(math.min(left.getValue, right.getValue))
              } else if (left.isValue && !right.isValue) {
                if (left.getValue <= right.min) {
                  warn(treePosHolder, InvariantExtrema(max = false, returnsFirst = true))
                  Values(left.getValue)
                } else if (left.getValue > right.max) {
                  warn(treePosHolder, InvariantExtrema(max = false, returnsFirst = false))
                  right
                } else {
                  Values.empty
                }
              } else if (!left.isValue && right.isValue) {
                if (right.getValue <= left.min) {
                  warn(treePosHolder, InvariantExtrema(max = false, returnsFirst = false))
                  Values(right.getValue)
                } else if (right.getValue > left.max) {
                  warn(treePosHolder, InvariantExtrema(max = false, returnsFirst = true))
                  left
                } else {
                  Values.empty
                }
              } else {
                Values.empty
              }
            } else if (op.toString == "take") {
              if (left.isSeq && left.actualSize != -1 && right.isValue) {
                if (right.getValueForce >= left.actualSize) {
                  warn(treePosHolder, UnnecessaryMethodCall("take"))
                  this
                } else {
                  if (right.getValueForce <= 0) warn(treePosHolder, ProducesEmptyCollection)
                  Values.empty.addName(name).addActualSize(math.max(0, right.getValueForce))
                }
              } else {
                Values.empty
              }
            } else if (op.toString == "drop") {
              if (left.isSeq && left.actualSize != -1 && right.isValue) {
                if (right.getValueForce <= 0) {
                  warn(treePosHolder, UnnecessaryMethodCall("drop"))
                  this
                } else {
                  if (left.actualSize-right.getValueForce <= 0) warn(treePosHolder, ProducesEmptyCollection)
                  Values.empty.addName(name).addActualSize(math.max(0, left.actualSize-right.getValueForce))
                }
              } else {
                Values.empty
              }
            } else if (left.isValue && right.isValue) {
              if (left.getValue == right.getValue && !left.name.isEmpty) {
                if (op == nme.SUB) warn(treePosHolder, OperationAlwaysProducesZero("subtraction"))
                if (op == nme.XOR) warn(treePosHolder, OperationAlwaysProducesZero("exclusive or"))
              }
              Values(func(left.getValue, right.getValue))
            } else if (!left.isValue && right.isValue) {
              left.map(a => func(a, right.getValue), rangeSafe = isRangeSafe)
            } else if (left.isValue && !right.isValue) {
              right.map(a => func(left.getValue, a), rangeSafe = isRangeSafe)
            } else {
              //ADD: join ranges, but be afraid of the explosion :)
              if (left eq right) {
                op match {
                  //case nme.ADD => left.map(a => a+a) // nope, wrong
                  //case nme.MUL => left.map(a => a*a)
                  case nme.DIV if (!left.contains(0)) => Values(1) //TODO: never gets executed?
                  case nme.SUB | nme.XOR =>
                    if (op == nme.SUB) warn(treePosHolder, OperationAlwaysProducesZero("subtraction"))
                    if (op == nme.XOR) warn(treePosHolder, OperationAlwaysProducesZero("exclusive or"))
                    Values(0)
                  case nme.AND | nme.OR  => left
                  case _ => Values.empty
                }
              } else {
                Values.empty
              }
            }
          )

          op match {
            case nme.MOD if right.isValue => out.addConditions(_ > -math.abs(right.getValue), _ < math.abs(right.getValue))
            case nme.DIV if left.isValue => out.addConditions(_ >= -math.abs(left.getValue), _ <= math.abs(left.getValue))
            /*case (nme.AND | nme.OR) if left.isValue || right.isValue =>
              //TODO: you need to learn two's complement, brah
              val (min, max) = (
                if (left.isValue && right.isValue)
                  (math.min(left.getValue, right.getValue), math.max(left.getValue, right.getValue))
                else if (left.isValue)
                  (left.getValue, left.getValue)
                else //if (right.isValue)
                  (right.getValue, right.getValue)
              )

              if (op == nme.AND) out.addConditions(_ <)*/
            case _ => out
          }
        }

        // Approximate size
        def size: Int = values.size + ranges.foldLeft(0)((acc, range) => acc + (range._2 - range._1) + 1)

        override def toString: String =
          "Values("+(if (name.size > 0) name+")(" else "")+(values.map(_.toString) ++ ranges.map(a => a._1+"-"+a._2)).mkString(",")+", "+isSeq+", "+actualSize+")"
      }

      val SeqLikeObject: Symbol = rootMirror.getModuleByName(newTermName("scala.collection.GenSeq"))
      val SeqLikeClass: Symbol = rootMirror.getClassByName(newTermName("scala.collection.SeqLike"))
      val SeqLikeContains: Symbol = SeqLikeClass.info.member(newTermName("contains"))
      val SeqLikeApply: Symbol = SeqLikeClass.info.member(newTermName("apply"))
      val SeqLikeGenApply: Symbol = SeqLikeObject.info.member(newTermName("apply"))
      def methodImplements(method: Symbol, target: Symbol): Boolean =
        try { method == target || method.allOverriddenSymbols.contains(target) } catch { case e: NullPointerException => false }

      //TODO: extend with more functions... and TEST TEST TEST TEST
      //// Attempt to compute part of the AST, and return Integer/List value
      def computeExpr(tree: Tree): Values = {
        if (doNotTraverse contains tree) return Values.empty
        treePosHolder = tree
        val out: Values = tree match {
          case Literal(Constant(value: Int)) => Values(value)
          //TODO: I don't think .this. ones work at all...
          case Select(This(_type), termName) =>
            val name = termName.toString
            val n = (if (name.contains(".this.")) name.substring(name.lastIndexOf('.')+1) else name).trim
            vals(n)
          case Ident(termName) =>
            val name = termName.toString
            val n = (if (name.contains(".this.")) name.substring(name.lastIndexOf('.')+1) else name).trim
            //println(n+": "+vals(n))
            if (vals contains n) vals(n) else if ((defModels contains n) && defModels(n).isLeft) defModels(n).left.get else Values.empty
          case Apply(Ident(termName), _) if defModels contains termName.toString =>
            val n = termName.toString
            if (vals contains n) vals(n) else if ((defModels contains n) && defModels(n).isLeft) defModels(n).left.get else Values.empty

          // String size
          /*case Apply(Select(Ident(id), length), Nil) if stringVals.exists(_.name == Some(id.toString)) && length.toString == "length" =>
            val exactValue = stringVals.find(_.name == Some(id.toString)).map(_.exactValue)
            exactValue.map(v => if (v.isDefined) Values(v.get.size) else Values.empty).getOrElse(Values.empty)

          case Select(Apply(Select(predef, augmentString), List(Ident(id))), size)
            if stringVals.exists(_.name == Some(id.toString)) && predef.toString == "scala.this.Predef" && augmentString.toString == "augmentString" && size.toString == "size" =>
            val exactValue = stringVals.find(_.name == Some(id.toString)).map(_.exactValue)
            exactValue.map(v => Values(v.size)).getOrElse(Values.empty)*/

          // String stuff (TODO: there's a copy up at applyCond)
          case _strFunc @ Apply(Select(string, func), params) if string.tpe.widen <:< StringClass.tpe =>
            StringAttrs.stringFunc(string, func, params).right.getOrElse(Values.empty)

          case _strFunc @ Select(Apply(scala_augmentString, List(string)), func)
            if (scala_augmentString.toString endsWith "augmentString") =>

            StringAttrs.stringFunc(string, func).right.getOrElse(Values.empty)

          case _strFunc @ Apply(Select(Apply(scala_augmentString, List(string)), func), params)
            if (scala_augmentString.toString endsWith "augmentString") =>

            StringAttrs.stringFunc(string, func, params).right.getOrElse(Values.empty)

            case Apply(Select(Apply(scala_StringContext_apply, _literalList), interpolator), _paramList)
              if (scala_StringContext_apply.toString == "scala.StringContext.apply")
              && (interpolator.toString == "s") =>

            StringAttrs(tree)

            Values.empty

          /// Division by zero
          case pos @ Apply(Select(num, op), List(expr))
            if (op == nme.DIV || op == nme.MOD)
            && (num.tpe.widen weak_<:< DoubleClass.tpe) && {
              val denom = computeExpr(expr)
              if (denom.isValue && denom.getValue == 1) {
                if (op == nme.DIV) {
                  warn(pos, DivideByOne)
                } else if (op == nme.MOD && (num.tpe.widen weak_<:< LongClass.tpe)) {
                  warn(pos, ModuloByOne)
                }

                false //Fallthrough
              } else if (denom.contains(0)) {
                warn(pos, DivideByZero)

                true
              } else {
                val numer = computeExpr(num)
                if (numer.isValue && numer.getValue == 0) {
                  warn(pos, ZeroDivideBy)
                }

                false //Fallthrough
              }
            } =>
            Values.empty

          // Range
          case Apply(Select(Apply(scala_Predef_intWrapper, List(Literal(Constant(low: Int)))), to_until), List(Literal(Constant(high: Int))))
            if (scala_Predef_intWrapper.toString endsWith "Wrapper") && (to_until.toString matches "to|until") =>

            val high2 = if (to_until.toString == "to") high else high-1
            new Values(Set((low, high2)), Set.empty, Set.empty, "", isSeq = true, high2-low)

          /// Use (low until high) instead of (low to high-1)
          case Apply(Select(Apply(scala_Predef_intWrapper, List(expr1)), to_until), List(expr2))
            if (scala_Predef_intWrapper.toString endsWith "Wrapper") =>

            if (to_until.toString == "to") {
              (expr1, expr2) match {
                case (Literal(Constant(_)), Apply(Select(expr, nme.SUB), List(Literal(Constant(1))))) =>
                  if (expr match {
                    case Ident(_) => true
                    case Select(Ident(_), size) if size.toString matches "size|length" => true // size value
                    case Apply(Select(Ident(_), size), Nil) if size.toString matches "size|length" => true // size getter
                    case Select(Apply(_implicitWrapper, List(Ident(_))), size) if size.toString matches "size|length" => true // wrapped size
                    case _ => false
                  }) warn(treePosHolder, UseUntilNotToMinusOne)
                case _ =>
              }
            }

            if ((to_until.toString matches "to|until") && computeExpr(expr1).isValue && computeExpr(expr2).isValue) {
              val (low, high) = (computeExpr(expr1).getValue, computeExpr(expr2).getValue + (if (to_until.toString == "to") 0 else -1))

              new Values(Set((low, high)), Set.empty, Set.empty, name = "", isSeq = true, actualSize = high-low)
            } else {
              Values.empty
            }

          case t @ Apply(TypeApply(genApply @ Select(_, _), _), genVals)
            if methodImplements(genApply.symbol, SeqLikeGenApply) && (!t.tpe.widen.toString.contains("collection.mutable.")) =>

            val values = genVals.map(computeExpr)
            if (values.forall(_.isValue)) new Values(values = values.map(_.getValue).toSet, isSeq = true, actualSize = values.size) else Values.empty.addActualSize(genVals.size)

          //TODO: Array isn't immutable
          //case Apply(Select(Select(scala, scala_Array), apply), genVals) if (scala_Array.toString == "Array") =>

          //TODO: is this for array or what?
          case Select(Apply(arrayOps @ Select(_, _intArrayOps), List(expr)), op) if (arrayOps.toString == staticSelect("scala", "Predef.intArrayOps")) =>
            computeExpr(expr).applyUnary(op)

          case Apply(TypeApply(t @ Select(_expr, _op), _), List(scala_math_Ordering_Int)) if scala_math_Ordering_Int.toString.endsWith("Int") => // .max .min
            computeExpr(t)

          case Apply(Select(scala_math_package, op), params) if scala_math_package.toString == "scala.math.`package`" =>
            op.toString match {
              case "abs"|"signum" if params.size == 1 => computeExpr(params.head).applyUnary(op)
              case "max"|"min"    if params.size == 2 => computeExpr(params(0)).applyBinary(op, computeExpr(params(1)))
              case _ => Values.empty
            }

          /// Parameter of Random.nextInt might be lower than 1 (runtime exception)
          case Apply(Select(scala_util_Random, nextInt), params) if nextInt.toString == "nextInt" && scala_util_Random.tpe <:< rootMirror.getClassByName(newTermName("scala.util.Random")).tpe =>
            if (params.size == 1) {
              val param = computeExpr(params.head)
              if (param.nonEmpty) {
                if (param.min <= 0) {
                  warn(treePosHolder, InvalidParamToRandomNextInt)
                  Values.empty
                } else {
                  Values(0, param.max-1)
                }
              } else {
                Values.empty
              }
            } else {
              //ADD: check what happens on +1, also if overflows are worthy of detection elsewhere :)
              //Values(Int.MinValue, Int.MaxValue)
              Values.empty
            }

          case Apply(TypeApply(Select(valName, op), _), List(scala_math_Numeric_IntIsIntegral))
            if scala_math_Numeric_IntIsIntegral.toString == "math.this.Numeric.IntIsIntegral" && op.toString == "sum" =>

            computeExpr(valName).applyUnary(op)

          //List(Literal(Constant(1)), Literal(Constant(2)), Literal(Constant(3)), Literal(Constant(4)), Literal(Constant(0)), Literal(Constant(0))))
          case Select(expr, op) =>
            //println((expr, op, computeExpr(expr).applyUnary(op)))
            computeExpr(expr).applyUnary(op)

          case Apply(Select(expr1, op), List(expr2)) =>
            //println("BinaryOp: "+(op, expr1, expr2, (computeExpr(expr1))(op)(computeExpr(expr2))))
            (computeExpr(expr1)).applyBinary(op, computeExpr(expr2))

          case Apply(Apply(TypeApply(Select(valName, map), List(_, _)), List(Function(List(ValDef(_, paramName, _, EmptyTree)), expr))), _) if (map.toString == "map") =>
            //List(TypeApply(Select(Select(This(newTypeName("immutable")), scala.collection.immutable.List), newTermName("canBuildFrom")), List(TypeTree()))))

            pushDefinitions()

            val res = computeExpr(valName)
            vals += paramName.toString -> res
            //println(">        "+vals)
            val out = computeExpr(expr)

            popDefinitions()
            //println(">        "+out)
            out

          case If(_, expr1, expr2) =>
            //TODO: if condExpr always/never holds, return that branch
            pushDefinitions()

            val e1 = computeExpr(expr1)

            popDefinitions()
            pushDefinitions()

            val e2 = computeExpr(expr2)

            popDefinitions()

            /*println(vals)
            vals = backupVals.map(a => (a._1, a._2.applyCond(condExpr)._1)).withDefaultValue(Values.empty)
            println("e1"+vals)
            val e1 = computeExpr(expr1)
            println("e1"+e1)
            stringVals = backupStrs

            vals = backupVals.map(a => (a._1, a._2.applyCond(condExpr)._2)).withDefaultValue(Values.empty)
            println("e2"+vals)
            val e2 = computeExpr(expr2)
            println("e2"+e1)*/

            //if (e1.isValue && e2.isValue) new Values(values = e1.values ++ e2.values) else Values.empty
            if (expr1.tpe <:< NothingClass.tpe) {
              e2
            } else if (expr2.tpe <:< NothingClass.tpe) {
              e1
            } else if (!e1.isSeq && !e2.isSeq && e1.nonEmpty && e2.nonEmpty) {
              new Values(values = e1.values ++ e2.values, ranges = e1.ranges ++ e2.ranges)
            } else {
              Values.empty
            }

          case _ =>
            //val raw = showRaw( a ); if (!exprs.contains(raw) && raw.size < 700 && raw.size > "EmptyTree".size)println("computeExpr: "+treePosHolder.toString+"\n"+raw); exprs += raw
            //for (Ident(id) <- a) if (stringVals.exists(_.name == Some(id.toString))) {println("id: "+id+"  "+showRaw( a )); }
            //println("computeExpr: "+showRaw( a ))
            Values.empty
        }
        //println(out+"  "+showRaw(tree))
        out
      }
      //val exprs = mutable.HashSet[String]()

      var vals = Map.empty[String, Values] withDefaultValue Values.empty
      var vars = Set.empty[String]
      var stringVals = Set.empty[StringAttrs]
      var defModels = Map.empty[String, Either[Values, StringAttrs]] withDefaultValue Left(Values.empty)
      var labels = Map.empty[String, Tree]
      def discardVars(): Unit = {
        for (v <- vars) vals += v -> Values.empty
      }
      def discardVars(tree: Tree): Unit = {
        for (v <- vars; if isAssigned(tree, v)) {
          vals += v -> Values.empty
          stringVals = stringVals.filter(v => v.name.isDefined && !(vars contains v.name.get))
        }
      }
      def discardVars(tree: Tree, force: Set[String]): Unit = {
        for (v <- vars; if isAssigned(tree, v) || (force contains v)) {
          vals += v -> Values.empty
          stringVals = stringVals.filter(v => v.name.isDefined && !(vars contains v.name.get))
        }
      }
      def discardVars(force: Set[String]): Unit = {
        for (v <- vars; if (force contains v)) {
          vals += v -> Values.empty
          stringVals = stringVals.filter(v => v.name.isDefined && !(vars contains v.name.get))
        }
      }

      // vals, vars, stringVals, defModels
      val backupStack = mutable.Stack[(Map[String, Values], Set[String], Set[StringAttrs], Map[String, Either[Values, StringAttrs]])]()
      def pushDefinitions(): Unit = {
        val nonUnitResult = backupStack.push((vals, vars, stringVals, defModels))
      }
      def popDefinitions(): Unit = {
        // Discards new and discarded vars also
        val varsCurr = vars
        val (valsBack, varsBack, stringValsBack, defModelsBack) = backupStack.pop
        vals = valsBack
        vars = varsBack
        discardVars((varsCurr | varsBack) -- (varsCurr & varsBack))
        stringVals = stringValsBack
        defModels = defModelsBack
      }

      def forLoop(tree: Tree): Unit = {
        treePosHolder = tree
        //TODO: actually anything that takes (A <: (a Number) => _), this is awful
        val funcs = "foreach|map|filter(Not)?|exists|find|flatMap|forall|groupBy|count|((drop|take)While)|(min|max)By|partition|span"

        val (param, values, body, func, collection) = tree match {
          case Apply(TypeApply(Select(collection, func), _), List(Function(List(ValDef(_, param, _, _)), body))) if (func.toString matches funcs) =>
            //println(showRaw(collection))
            val values = computeExpr(collection).addName(param.toString)
            //println(values)
            //if (values.isEmpty) {
                //println("not a collection I know("+tree.pos+"): "+showRaw(collection))
                //return
            //}

            (param.toString, values, body, func.toString, collection)
          case _ =>
            //println("not a loop("+tree.pos+"): "+showRaw(tree))
            return
        }

        if (values.nonEmpty) {
          vals += param -> values.notSeq

          val exceptions =
            (func == "foreach" || param.contains("$") || collection.tpe.toString.startsWith("scala.collection.immutable.Range"))

          //TODO: verify filter = ...
          if (!isUsed(body, param, filter = "size|length|head|last") && !exceptions) warn(tree, UnusedForLoopIteratorValue)

          traverseBlock(body)
        }
      }

      val doNotTraverse = mutable.HashSet[Tree]()

      //implicit def String2StringAttrs(s: String) = new StringAttrs(exactValue = Some(s))
      //// String abstract interpreter
      object StringAttrs {
        //TODO: when merging, save known chunks - .contains then partially works
        def empty: StringAttrs = new StringAttrs()

        // scalastyle:off magic.number
        def toStringAttrs(param: Tree): StringAttrs = {
          val intParam = computeExpr(param)
          if (param.tpe.widen <:< IntClass.tpe && intParam.size >= 1) {
            if (intParam.isValue) {
              new StringAttrs(exactValue = Some(intParam.getValue.toString))
            } else {
              val maxLen = math.max(intParam.max.toString.length, intParam.min.toString.length)
              new StringAttrs(minLength = 1, trimmedMinLength = 1, maxLength = maxLen, trimmedMaxLength = maxLen)
            }
          } else if (param match { case Literal(Constant(_)) => true case _  => false }) param match { // groan.
            case Literal(Constant(null)) => new StringAttrs(exactValue = Some("null"))
            case Literal(Constant(a))    => new StringAttrs(Some(a.toString))
          } else if (param.tpe.widen <:< CharClass.tpe)  new StringAttrs(minLength = 1, trimmedMinLength = 0, maxLength = 1, trimmedMaxLength = 0)
          else if (param.tpe.widen <:< ByteClass.tpe)    new StringAttrs(minLength = 1, trimmedMinLength = 1, maxLength = 4, trimmedMaxLength = 4)
          else if (param.tpe.widen <:< ShortClass.tpe)   new StringAttrs(minLength = 1, trimmedMinLength = 1, maxLength = 6, trimmedMaxLength = 6)
          else if (param.tpe.widen <:< IntClass.tpe)     new StringAttrs(minLength = 1, trimmedMinLength = 1, maxLength = 11, trimmedMaxLength = 11)
          else if (param.tpe.widen <:< LongClass.tpe)    new StringAttrs(minLength = 1, trimmedMinLength = 1, maxLength = 20, trimmedMaxLength = 20)
          // http://stackoverflow.com/questions/1701055/what-is-the-maximum-length-in-chars-needed-to-represent-any-double-value :)
          else if (param.tpe.widen <:< DoubleClass.tpe)  new StringAttrs(minLength = 1, trimmedMinLength = 1, maxLength = 1079, trimmedMaxLength = 1079)
          else if (param.tpe.widen <:< FloatClass.tpe)   new StringAttrs(minLength = 1, trimmedMinLength = 1, maxLength = 154, trimmedMaxLength = 154)
          else if (param.tpe.widen <:< BooleanClass.tpe) new StringAttrs(minLength = 4, trimmedMinLength = 4, maxLength = 5, trimmedMaxLength = 5)
          else {
            if (!(param.tpe.widen <:< StringClass.tpe) && (param.tpe.baseClasses exists { _.tpe =:= TraversableClass.tpe })) {
              // Collections: minimal is Nil or Type()

              // Adapted from src/library/scala/collection/TraversableLike.scala
              val stringPrefix = {
                var string = param.tpe.toString
                val idx1 = string.lastIndexOf('.' : Int)
                if (idx1 != -1) string = string.substring(idx1 + 1)
                val idx2 = string.indexOf('$')
                if (idx2 != -1) string = string.substring(0, idx2)
                val idx3 = string.indexOf('[')
                if (idx3 != -1) string = string.substring(0, idx3)

                //Workaround: for Nil and for Seq which goes to List or ArrayBuffer
                if (string == "type" || string == "Seq") "" else string + "("
              }

              val minLen = stringPrefix.length+2
              new StringAttrs(
                minLength = minLen,
                trimmedMinLength = minLen,
                prefix = stringPrefix,
                suffix = ")",
                knownPieces = Set(stringPrefix, ")"))
            } else {
              //TODO: Discover moar
              //if (!(param.tpe.widen <:< StringClass.tpe) && !(param.tpe.widen <:< AnyClass.tpe))println(((str, param), (param.tpe, param.tpe.widen)))

              StringAttrs(param)
            }
          }
        }
        // scalastyle:on magic.number

        //// Tries to execute string functions and returns either a String or Int representation
        def stringFunc(string: Tree, func: Name, params: List[Tree] = List.empty[Tree]): Either[StringAttrs, Values] = {
          val str = StringAttrs(string)
          val paramsSize = params.size
          lazy val intParam = if (paramsSize == 1 && params.head.tpe.widen <:< IntClass.tpe) computeExpr(params.head) else Values.empty
          lazy val intParams = if (params.forall(_.tpe.widen <:< IntClass.tpe)) params.map(computeExpr) else Nil //option?
          lazy val stringParam: StringAttrs = if (paramsSize == 1 && params.head.tpe.widen <:< StringClass.tpe) StringAttrs(params.head) else empty
          lazy val stringParams: Seq[StringAttrs] = if (params.forall(_.tpe.widen <:< StringClass.tpe)) params.map(StringAttrs.apply) else Nil

          //println((string, func, params, str, intParam))
          //println(str.exactValue)
          //if (!(string.tpe.widen <:< StringClass.tpe))
          //if ((string.tpe.widen <:< StringClass.tpe))println((string, func, params, str, intParam))

          // We can get some information, even if the string is unknown
          //if (str == StringAttrs.empty) {
          //  Left(empty)
          //} else
          func.toString match {
            case "size"|"length" if paramsSize == 0 =>
              Right(
                str.exactValue
                  .map(v => Values(v.size))
                  .getOrElse(
                    if (str.getMinLength == str.getMaxLength)
                      Values(str.getMinLength)
                    else if (str.getMinLength == 0 && str.getMaxLength == Int.MaxValue)
                      Values.empty
                    else
                      Values(str.getMinLength, str.getMaxLength)
                  ).addCondition(_ >= 0).addCondition(_ < str.getMaxLength)
              )
            case "toString" if paramsSize == 0 =>
              Left(toStringAttrs(string))
            case "$plus"|"concat" if paramsSize == 1 =>
              //println(str.toString +" + "+toStringAttrs(params.head).toString + " == "+ (str + toStringAttrs(params.head)).toString)
              Left(str + toStringAttrs(params.head))
            case "$times" =>
              Left(str * intParam)

            case "intern" =>
              Left(str)

            case f @ ("head"|"last") =>
              if (str.maxLength == 0) {
                warn(treePosHolder, DecomposingEmptyCollection(f, "string"))
              }

              Left(empty)

            case f @ ("init"|"tail") =>
              if (str.exactValue.isDefined) {
                if (str.exactValue.get.isEmpty) {
                  warn(treePosHolder, DecomposingEmptyCollection(f, "string"))
                  Left(empty)
                } else {
                  Left(new StringAttrs(str.exactValue.map(a => if (f == "init") a.init else a.tail)))
                }
              } else {
                if (str.maxLength == 0) {
                  warn(treePosHolder, DecomposingEmptyCollection(f, "string"))
                }

                Left(new StringAttrs(
                  minLength = math.max(str.minLength-1, 0),
                  maxLength = if (str.maxLength != Int.MaxValue) math.max(str.maxLength-1, 0) else Int.MaxValue))
              }

            case "capitalize" if paramsSize == 0 => Left(str.capitalize)
            case "distinct"   if paramsSize == 0 => Left(str.distinct)
            case "reverse"    if paramsSize == 0 => Left(str.reverse)
            case "count"      if paramsSize == 1 =>
              val out = Values.empty.addCondition(_ >= 0)
              Right(if (str.getMaxLength != Int.MaxValue) out.addCondition(_ < str.getMaxLength) else out)
            case ("filter" | "filterNot") if paramsSize == 1 =>
              Left(str.removeExactValue.zeroMinLengths)

            case f @ ("indexOf"|"lastIndexOf") if paramsSize == 1 =>
              if (str.exactValue.isDefined && stringParam.exactValue.isDefined) {
                if (f == "indexOf")
                  Right(Values(str.exactValue.get.indexOf(stringParam.exactValue.get)))
                else
                  Right(Values(str.exactValue.get.lastIndexOf(stringParam.exactValue.get)))
              } else if (str.getMaxLength < Int.MaxValue) {
                Right(Values.empty.addConditions(_ >= -1, _ < str.getMaxLength))
              } else {
                Right(Values.empty.addConditions(_ >= -1))
              }

            // These also come in (Char/String) versions
            case "stringPrefix" if paramsSize == 0 && str.exactValue.isDefined => Left(new StringAttrs(str.exactValue.map(_.stringPrefix)))
            case "stripLineEnd" if paramsSize == 0 && str.exactValue.isDefined => Left(new StringAttrs(str.exactValue.map(_.stripLineEnd)))
            case "stripMargin"  if paramsSize == 0 && str.exactValue.isDefined => Left(new StringAttrs(str.exactValue.map(_.stripMargin)))

            //
            case "mkString" if paramsSize == 0 =>
                warn(treePosHolder, UnnecessaryMethodCall("mkString"))

                Left(str)
            case "mkString" if paramsSize == 1 && str.exactValue.isDefined && stringParam.exactValue.isDefined =>
                val p0 = stringParam.exactValue.get

                Left(new StringAttrs(Some(str.exactValue.get.mkString(p0))))
            //
            case "toUpperCase" => Left(str.toUpperCase)
            case "toLowerCase" => Left(str.toLowerCase)
            case "trim" =>  Left(str.trim)
            case "nonEmpty"|"isEmpty" =>
              if (str.alwaysNonEmpty) warn(treePosHolder, UnnecessaryStringNonEmpty)
              if (str.alwaysIsEmpty) warn(treePosHolder, UnnecessaryStringIsEmpty)
              Left(empty)
            case "hashCode" if str.exactValue.isDefined =>
              Right(Values(str.exactValue.get.hashCode))
            /// String.to{Int, Long, Float, Double} conversion will likely fail (runtime exception)
            case f @ "toInt" if str.exactValue.isDefined =>
              try {
                Right(Values(str.exactValue.get.toInt))
              } catch {
                case e: Exception =>
                  warn(treePosHolder, InvalidStringConversion(f))
                  Left(empty)
              }
            case f @ ("toLong") if str.exactValue.isDefined =>
              try {
                str.exactValue.get.toLong
              } catch {
                case e: Exception =>
                  warn(treePosHolder, InvalidStringConversion(f))
              }
              Left(empty)
            case f @ ("toDouble"|"toFloat") if str.exactValue.isDefined =>
              try {
                str.exactValue.get.toDouble
              } catch {
                case e: Exception =>
                  warn(treePosHolder, InvalidStringConversion(f))
              }
              Left(empty)

            // str.func(String)
            case f @ ("charAt"|"codePointAt"|"codePointBefore"|"substring"
                     |"apply"|"drop"|"take"|"dropRight"|"takeRight") if intParam.isValue =>
              val param = intParam.getValue
              lazy val string = str.exactValue.get //lazy to avoid None.get... didn't use monadic, because I was lazy

              //println((string, param))

              //TODO: use reflection maybe?
              //TODO: could do some prefix/suffix enhancements
              try f match {
                case "charAt"|"apply" =>
                  if (str.exactValue.isDefined) { string.charAt(param); Left(empty) } else if (param < 0) throw new IndexOutOfBoundsException else Left(empty)
                case "codePointAt" =>
                  if (str.exactValue.isDefined) Right(Values(string.codePointAt(param))) else if (param < 0) throw new IndexOutOfBoundsException else Left(empty)
                case "codePointBefore" =>
                  if (str.exactValue.isDefined) Right(Values(string.codePointBefore(param))) else if (param < 1) throw new IndexOutOfBoundsException else Left(empty)
                case "substring" =>
                  if (str.exactValue.isDefined) Left(new StringAttrs(Some(string.substring(param)))) else if (param < 0) throw new IndexOutOfBoundsException else Left(empty)
                case "drop" =>
                  if (str.exactValue.isDefined)
                    Left(new StringAttrs(Some(string.drop(param))))
                  else
                    Left(new StringAttrs(minLength = math.max(str.minLength-param, 0), maxLength = if (str.maxLength != Int.MaxValue) math.max(str.maxLength-param, 0) else Int.MaxValue))
                case "take" =>
                  if (str.exactValue.isDefined)
                    Left(new StringAttrs(Some(string.take(param))))
                  else
                    Left(new StringAttrs(minLength = math.min(param, str.minLength), maxLength = math.max(param, 0)))
                case "dropRight" =>
                  if (str.exactValue.isDefined)
                    Left(new StringAttrs(Some(string.dropRight(param))))
                  else
                    Left(new StringAttrs(minLength = math.max(str.minLength-param, 0), maxLength = if (str.maxLength != Int.MaxValue) math.max(str.maxLength-param, 0) else Int.MaxValue))
                case "takeRight" =>
                  if (str.exactValue.isDefined)
                    Left(new StringAttrs(Some(string.takeRight(param))))
                  else
                    Left(new StringAttrs(minLength = math.min(param, str.minLength), maxLength = math.max(param, 0)))
                case _ =>
                  Left(empty)
              } catch {
                case e: IndexOutOfBoundsException =>
                  /// String index will likely cause an IndexOutOfBoundsException (runtime exception)
                  warn(params.head, LikelyIndexOutOfBounds("out of bounds"))
                  Left(empty)
                case e: Exception =>
                  Left(empty)
              }

            //str.func(Int, Int)
            case f @ ("substring"|"codePointCount") if intParams.size == 2 && intParams.forall(_.isValue) =>
              lazy val string = str.exactValue.get //lazy to avoid None.get... didn't use monadic, because I was lazy
              val param: Seq[Int] = intParams.map(_.getValue)

              //println((string, param))

              try f match {
                case "substring" =>
                  if (str.exactValue.isDefined)
                    Left(new StringAttrs(Some(string.substring(param(0), param(1)))))
                  else if (param(0) < 0 || param(1) < param(0) || param(0) >= str.getMaxLength || param(1) >= str.getMaxLength)
                    throw new IndexOutOfBoundsException
                  else if (param(0) == param(1))
                    Left(new StringAttrs(Some("")))
                  else
                    Left(empty)
                case "codePointCount" =>
                  if (str.exactValue.isDefined)
                    Right(Values(string.codePointCount(param(0), param(1))))
                  else if (param(0) < 0 || param(1) < param(0) || param(0) >= str.getMaxLength || param(1) >= str.getMaxLength)
                    throw new IndexOutOfBoundsException
                  else
                    Left(empty)
                case _ => Left(empty)
              } catch {
                case e: IndexOutOfBoundsException =>
                  warn(params.head, LikelyIndexOutOfBounds("out of bounds"))
                  Left(empty)
                case e: Exception =>
                  Left(empty)
              }

            // str.func(String)
            /// Try to verify String contains, startsWith, endsWith
            case func @ ("contains"|"startsWith"|"endsWith"|"equals"|"$eq$eq"|"$bang$eq"|"matches") =>
              val result = func match {
                case "contains"   => (str contains stringParam)
                case "startsWith" => (str startsWith stringParam)
                case "endsWith"   => (str endsWith stringParam)
                case "equals"|"$eq$eq" => (str equals stringParam)
                case "$bang$eq" => (str nequals stringParam)
                case "matches" => (str matches stringParam)
                case _ => None
              }
              val function = if (func == "$eq$eq") "equals" else if (func == "$bang$eq") "not equals" else func
              if (result.isDefined) warn(params.head, InvariantReturn(function, result.get.toString))

              Left(empty)

            //str.func(String, String)
            case "replace" if stringParams.size == 2 && stringParams.forall(_.exactValue.isDefined) =>
              val (p0, p1) = (stringParams(0).exactValue.get, stringParams(1).exactValue.get)

              if (p0 == p1) {
                warn(treePosHolder, UnnecessaryMethodCall("replace"))

                Left(str)
              } else if (str.exactValue.isDefined) {
                Left(new StringAttrs(Some(str.exactValue.get.replace(p0, p1))))
              } else {
                Left(empty)
              }

            case f @ ("replaceAll"|"replaceFirst") if stringParams.size == 2 && stringParams.forall(_.exactValue.isDefined) =>
              val (p0, p1) = (stringParams(0).exactValue.get, stringParams(1).exactValue.get)

              if (p1.isEmpty) {
                //TODO: nopenopenopenope - matches the end of replaceX func, because this gets traversed multiple times with the wrong pos
                val posFiltering = treePosHolder.toString matches (".*?[ .]"+ f + """ *[(].*, *("{2}|"{6}) *[)]""")

                val special = """.\^$*+?()\[{\\|"""
                val plainString = s"""([^$special]|[\\\\][$special])+"""

                if (posFiltering && (p0 matches plainString+"\\$"))
                  warn(treePosHolder, RegexWarning(s"This $f can be substituted with stripSuffix", error = false))
                if (posFiltering && (p0 matches "\\^"+plainString))
                  warn(treePosHolder, RegexWarning(s"This $f can be substituted with stripPrefix", error = false))
              }

              if (str.exactValue.isDefined) {
                try {
                  Left(new StringAttrs(Some(if (f == "replaceAll") str.exactValue.get.replaceAll(p0, p1) else str.exactValue.get.replaceFirst(p0, p1))))
                } catch {
                  case e: java.util.regex.PatternSyntaxException =>
                    Left(empty)
                }
              } else if ((p0 matches """\[[^\]]+\]""") && p1.size == 1) { //keeps the same length
                Left(str.justLengths)
              } else if (p1 == "") { //length is 0..size
                Left(str.justLengths.zeroMinLengths)
              } else {
                Left(empty)
              }
            /// String format checks (runtime exception)
            case f @ "format"
              if (params.nonEmpty) && !(params.head.tpe.widen <:< rootMirror.getClassByName(newTermName("java.util.Locale")).tpe) =>
              //TODO: see http://docs.oracle.com/javase/7/docs/api/java/util/Formatter.html
              // in some cases at least length could be inferred, but option parser would need to be implemented

              val parActual = params map {
                case param if (param.tpe.widen <:< IntClass.tpe) => computeExpr(param)
                case param if (param.tpe.widen <:< StringClass.tpe) => StringAttrs(param)
                case Literal(Constant(x)) => x
                case _ => StringAttrs.empty
              }

              val valuesDefined = parActual forall {
                case v: Values => v.isValue
                case s: StringAttrs => s.exactValue.isDefined // Nonliterals are StringAttrs.empty, and go to false here
                case _ => true // Literals are defined
              }

              val prefixReg = "([^%]*).*".r
              val suffixReg = ".*?[^%]( [^%]*)".r
              def getPrefix(s: String): String = try { val prefixReg(out) = s; out } catch { case e: MatchError => "" }
              def getSuffix(s: String): String = try { val suffixReg(out) = s; out } catch { case e: MatchError => "" }

              var outStr = Left(empty)
              if (str.exactValue.isDefined) {
                val strValue = str.exactValue.get
                val percentCnt = strValue.count(_ == '%')
                if (percentCnt == 0) {
                  warn(string, InvalidStringFormat("There are no percent signs in the format string.", exception = false))
                } else if (parActual.length > percentCnt) {
                  warn(string, InvalidStringFormat("There are more parameters being passed to format, than there are percent signs in the format string.", exception = false))
                }
                if (valuesDefined) {
                  try {
                    outStr = Left(new StringAttrs(exactValue = Some(strValue.format(parActual map {
                      case v: Values => v.getValue
                      case s: StringAttrs => s.exactValue.get
                      case x => x
                    }:_*))))
                  } catch {
                    case e: java.util.UnknownFormatConversionException =>
                      warn(string, InvalidStringFormat(e.toString))
                    case e: java.util.IllegalFormatConversionException if !e.getMessage.contains("!= java.lang.String") => //TODO: Why this condition?
                      warn(string, InvalidStringFormat(e.toString))
                    case e: java.util.MissingFormatArgumentException =>
                      warn(string, InvalidStringFormat(e.toString))
                    case e: java.util.DuplicateFormatFlagsException =>
                      warn(string, InvalidStringFormat(e.toString))
                    case e: Exception =>
                  }
                } else {
                  try {
                    strValue.format(parActual.map { a => null }:_*)
                    val prefix = getPrefix(strValue)
                    val suffix = getSuffix(strValue)
                    outStr = Left(new StringAttrs(
                      minLength = math.max(prefix.length, suffix.length),
                      trimmedMinLength = math.max(prefix.trim.length, suffix.trim.length),
                      prefix = prefix,
                      suffix = suffix,
                      knownPieces = Set(prefix, suffix)))
                  } catch {
                    case e: java.util.UnknownFormatConversionException =>
                      warn(string, InvalidStringFormat(e.toString))
                    case e: java.util.MissingFormatArgumentException =>
                      warn(string, InvalidStringFormat(e.toString))
                    case e: java.util.DuplicateFormatFlagsException =>
                      warn(string, InvalidStringFormat(e.toString))
                    case e: Exception =>
                  }
                }
              } else if (str.prefix != "" || str.suffix != "") {
                val prefix = str.prefix
                val suffix = str.suffix
                outStr = Left(new StringAttrs(
                  minLength = math.max(prefix.length, suffix.length),
                  trimmedMinLength = math.max(prefix.trim.length, suffix.trim.length),
                  prefix = prefix,
                  suffix = suffix,
                  knownPieces = Set(prefix, suffix)))
              }

              outStr
            case _ =>
              //if (str.exactValue.isDefined)println((str, func, params))
              Left(empty)
          }
        }


        // avoids an undebugged infinite loop
        val cache = mutable.Map[Tree, StringAttrs]()

        //// Try to convert an AST into a String value
        def apply(tree: Tree): StringAttrs = {
          def traverseString(tree: Tree): StringAttrs = tree match {
            case Literal(Constant(null)) => new StringAttrs(exactValue = Some("null"))
            case Literal(Constant(c)) =>
              //if (stringVals.filter(s => s.name.isDefined && !(vars contains s.name.get)).exists(_.exactValue == Some(c.toString))) {
                //warn(tree, "You have defined that string as a val already, maybe use that?")
              //}

              new StringAttrs(exactValue = Some(c.toString))

            case Ident(name) =>
              stringVals
                .find(_.name.exists(_ == name.toString))
                .getOrElse(empty)

            // String interpolator
            case Apply(Select(Apply(scala_StringContext_apply, literalList), interpolator), paramList)
              if (scala_StringContext_apply.toString == "scala.StringContext.apply")
              && (interpolator.toString == "s") =>

              try {
                if (paramList.isEmpty) {
                  warn(tree, EmptyStringInterpolator)
                  apply(literalList.head)
                } else {
                  var out = empty
                  var i = 0
                  do {
                    out +=
                      (if (i%2 == 0 && i/2 < literalList.size) apply(literalList(i/2))
                      else if (i/2 < literalList.size) apply(paramList(i/2))
                      else new StringAttrs(exactValue = Some("")))

                    i += 1
                  } while (i < literalList.length + paramList.length)
                  out
                }
              } catch {
                case e: Exception =>
                  empty
              }

            case Apply(Ident(name), _params) if defModels contains name.toString =>
              defModels
                .find(m => m._1 == name.toString && m._2.isRight).fold(empty)(_._2.right.get)

            case If(_cond, expr1, expr2) =>
              val (e1, e2) = (traverseString(expr1), traverseString(expr2))

              if (expr1.tpe <:< NothingClass.tpe) {
                e2
              } else if (expr2.tpe <:< NothingClass.tpe) {
                e1
              } else {
                new StringAttrs(
                  minLength = math.min(e1.getMinLength, e2.getMinLength),
                  trimmedMinLength = math.min(e1.getTrimmedMinLength, e2.getTrimmedMinLength),
                  maxLength = math.max(e1.getMaxLength, e2.getMaxLength),
                  trimmedMaxLength = math.max(e1.getTrimmedMaxLength, e2.getTrimmedMaxLength))
              }

            case Apply(augmentString, List(expr)) if (augmentString.toString == staticSelect("scala", "Predef.augmentString")) =>
              StringAttrs(expr)

            // Implicit toString
            case Apply(Select(expr1, nme.ADD), List(expr2)) if (expr1.tpe.widen <:< StringClass.tpe ^ expr2.tpe.widen <:< StringClass.tpe) =>
              toStringAttrs(expr1) + toStringAttrs(expr2)

            // Pass on functions on strings
            //TODO: maybe check if some return string and can be computed
            case Apply(Select(str, func), params) =>
              cache.getOrElseUpdate(tree, stringFunc(str, func, params).left.getOrElse(empty))

            case Select(Apply(scala_augmentString, List(string)), func) if (scala_augmentString.toString endsWith "augmentString") =>
              stringFunc(string, func).left.getOrElse(empty)

            case _ =>
              //println(showRaw(a))
              empty
          }

          val a = traverseString(tree)
          //println("tree: "+ a)
          a
        }
      }
      class StringAttrs(
          val exactValue: Option[String] = None,
          val name: Option[String] = None, // Move to outer map?
          private val minLength: Int = 0,
          private val trimmedMinLength: Int = 0,
          private val maxLength: Int = Int.MaxValue,
          private val trimmedMaxLength: Int = Int.MaxValue,
          private var prefix: String = "",
          private var suffix: String = "",
          private var knownPieces: Set[String] = Set.empty[String]) {

        // Keep this in mind, make getters
        if (exactValue.isDefined) {
          prefix = exactValue.get
          suffix = exactValue.get
          knownPieces = Set.empty[String]
        }
        //println(this)

        def equals(s: StringAttrs): Option[Boolean] =
          if (s.getMinLength > this.getMaxLength || this.getMinLength > s.getMaxLength) Some(false)
          else if (s.exactValue.isDefined) this.equals(s.exactValue.get)
          else if (this.exactValue.isDefined) s.equals(this.exactValue.get)
          else None
        def equals(s: String): Option[Boolean] = {
          if (exactValue.isDefined) {
            Some(exactValue.get == s)
          } else {
            if((s.length < getMinLength)
            || (s.length > getMaxLength)
            || !(s startsWith prefix)
            || !(s endsWith suffix)
            || !(knownPieces forall { s contains _ }))
              Some(false)
            else
              None
          }
        }

        def nequals(s: StringAttrs): Option[Boolean] = equals(s).map(equal => !equal)
        def nequals(s: String): Option[Boolean] = equals(s).map(equal => !equal)

        def contains(s: StringAttrs): Option[Boolean] =
          if (s.getMinLength > getMaxLength) Some(false)
          else if (s.exactValue.isDefined) this.contains(s.exactValue.get)
          else None
        def contains(s: String): Option[Boolean] =
          if (exactValue.isDefined) Some(exactValue.get contains s)
          else if ((prefix contains s) || (suffix contains s) || (knownPieces exists { _ contains s })) Some(true)
          else if (s.length > getMaxLength) Some(false)
          else None

        def startsWith(s: StringAttrs): Option[Boolean] =
          if (s.getMinLength > getMaxLength) Some(false)
          else if (s.exactValue.isDefined) this.startsWith(s.exactValue.get)
          else None
        def startsWith(s: String): Option[Boolean] =
          if (s.length > getMaxLength) Some(false)
          else if (prefix.isEmpty || s.length > prefix.length) None
          else Some(prefix startsWith s)

        def endsWith(s: StringAttrs): Option[Boolean] =
          if (s.getMinLength > getMaxLength) Some(false)
          else if (s.exactValue.isDefined) this.endsWith(s.exactValue.get)
          else None
        def endsWith(s: String): Option[Boolean] =
          if (s.length > getMaxLength) Some(false)
          else if (suffix.isEmpty || s.length > suffix.length) None
          else Some(suffix endsWith s)

        def matches(s: StringAttrs): Option[Boolean] = {
          if ((s startsWith "^") == Some(true) || ((s endsWith "$") == Some(true) && (s.suffix.length == 1 || (s endsWith "\\$") == Some(false)))) warn(treePosHolder, SuspiciousMatches)

          if (s.exactValue.isDefined) this.matches(s.exactValue.get)
          else None
        }
        def matches(s: String): Option[Boolean] = {
          if ((s startsWith "^") || ((s endsWith "$") && !(s endsWith "\\$"))) warn(treePosHolder, SuspiciousMatches)

          if (exactValue.isDefined) Some(exactValue.get matches s)
          else None
        }

        def capitalize: StringAttrs =
          new StringAttrs(
            exactValue = exactValue.map { _.capitalize },
            minLength = getMinLength,
            trimmedMinLength = getTrimmedMinLength,
            maxLength = getMaxLength,
            trimmedMaxLength = getTrimmedMaxLength,
            prefix = prefix.capitalize,
            suffix = suffix,
            knownPieces = knownPieces filterNot { _ contains suffix })

        def distinct: StringAttrs =
          new StringAttrs(
            exactValue = exactValue.map { _.distinct },
            minLength = math.min(getMinLength, 1),
            maxLength = getMaxLength,
            trimmedMaxLength = getTrimmedMaxLength)

        def reverse: StringAttrs =
          new StringAttrs(
            exactValue = exactValue.map { _.reverse },
            minLength = getMinLength,
            trimmedMinLength = getTrimmedMinLength,
            maxLength = getMaxLength,
            trimmedMaxLength = getTrimmedMaxLength,
            prefix = suffix.reverse,
            suffix = prefix.reverse,
            knownPieces = knownPieces map { _.reverse })

        def trim: StringAttrs = {
          val newMinLength = exactValue.fold(getTrimmedMinLength)(_.trim.size)
          val newMaxLength = exactValue.fold(getTrimmedMaxLength)(_.trim.size)
          new StringAttrs(
            exactValue = exactValue.map { _.trim },
            minLength = newMinLength,
            trimmedMinLength = newMinLength,
            maxLength = newMaxLength,
            trimmedMaxLength = newMaxLength)//TODO: prefix/suffix trimleft/right
        }

        def toUpperCase: StringAttrs =
          new StringAttrs(
            exactValue = exactValue.map { _.toUpperCase },
            minLength = getMinLength,
            trimmedMinLength = getTrimmedMinLength,
            maxLength = getMaxLength,
            trimmedMaxLength = getTrimmedMaxLength,
            prefix = prefix.toUpperCase,
            suffix = suffix.toUpperCase,
            knownPieces = knownPieces map { _.toUpperCase })
        def toLowerCase: StringAttrs =
          new StringAttrs(
            exactValue = exactValue.map { _.toLowerCase },
            minLength = getMinLength,
            trimmedMinLength = getTrimmedMinLength,
            maxLength = getMaxLength,
            trimmedMaxLength = getTrimmedMaxLength,
            prefix = prefix.toLowerCase,
            suffix = suffix.toLowerCase,
            knownPieces = knownPieces map { _.toLowerCase })

        def addName(name: String): StringAttrs = new StringAttrs(exactValue, Some(name), getMinLength, trimmedMinLength, getMaxLength, trimmedMaxLength, prefix, suffix, knownPieces)
        def removeExactValue: StringAttrs = new StringAttrs(None, name, getMinLength, trimmedMinLength, getMaxLength, trimmedMaxLength)
        def zeroMinLengths: StringAttrs = new StringAttrs(exactValue, name, 0, 0, getMaxLength, trimmedMaxLength, prefix, suffix, knownPieces)
        def justLengths: StringAttrs = new StringAttrs(None, name, getMinLength, getTrimmedMinLength, getMaxLength, trimmedMaxLength, "", "", Set.empty)

        def alwaysIsEmpty: Boolean = getMaxLength == 0
        def alwaysNonEmpty: Boolean = getMinLength > 0

        def getMinLength: Int = exactValue.fold(minLength)(_.size)
        def getMaxLength: Int = exactValue.fold(maxLength)(_.size)
        def getTrimmedMinLength: Int = exactValue.fold(trimmedMinLength)(_.trim.size)
        def getTrimmedMaxLength: Int = exactValue.fold(trimmedMaxLength)(_.trim.size)

        def +(s: String): StringAttrs =
          new StringAttrs(
            exactValue = if (this.exactValue.isDefined) Some(this.exactValue.get + s) else None,
            minLength = this.getMinLength + s.length,
            trimmedMinLength = this.getTrimmedMinLength + s.trim.length, //TODO: can be made more exact
            maxLength = if (this.maxLength == Int.MaxValue) Int.MaxValue else this.getMaxLength + s.length,
            trimmedMaxLength =
              if (this.getTrimmedMaxLength == Int.MaxValue) Int.MaxValue
              //else if (this.exactValue.isDefined) (this.exactValue.get + s).trim.size // This case is covered in getTrimmedMaxLength if exactValue is known
              else this.getMaxLength + s.length,
            prefix = if (this.exactValue.isDefined) this.exactValue.get + s else this.prefix,
            suffix = this.suffix + s,
            knownPieces = this.knownPieces)

        def +(s: StringAttrs): StringAttrs =
          new StringAttrs(
            exactValue = if (this.exactValue.isDefined && s.exactValue.isDefined) Some(this.exactValue.get + s.exactValue.get) else None,
            minLength = this.getMinLength + s.getMinLength,
            trimmedMinLength = this.getTrimmedMinLength + s.getTrimmedMinLength, //TODO: can be made more exact
            maxLength = if (this.getMaxLength == Int.MaxValue || s.getMaxLength == Int.MaxValue) Int.MaxValue else this.getMaxLength + s.getMaxLength,
            trimmedMaxLength =
              if (this.getTrimmedMaxLength == Int.MaxValue || s.getTrimmedMaxLength == Int.MaxValue) Int.MaxValue
              //else if (this.isDefined && s.isDefined) (this.exactValue.get + s.exactValue.get).trim.size // This case is covered in getTrimmedMaxLength if exactValue is known
              else this.getMaxLength + s.getMaxLength,
            prefix = if (this.exactValue.isDefined) this.exactValue.get + s.prefix else this.prefix,
            suffix = if (s.exactValue.isDefined) this.suffix + s.suffix else s.suffix,
            knownPieces = this.knownPieces ++ s.knownPieces + (this.suffix + s.prefix))

        /// String multiplication with value <= 0 warning
        def *(n: Values): StringAttrs = {
          if (n.isValue) {
            this * n.getValue
          } else if (n.nonEmpty) {
            if (n forallLower 2) {
              if (n.max == 1) {
                new StringAttrs(
                  minLength = 0,
                  trimmedMinLength = 0,
                  maxLength = this.getMaxLength,
                  trimmedMaxLength = this.getTrimmedMaxLength)
              } else {
                warn(treePosHolder, StringMultiplicationByNonPositive)
                new StringAttrs(exactValue = Some(""))
              }
            } else {
              new StringAttrs(
                minLength = if (n.min > 0) this.getMinLength*n.min else 0,
                trimmedMinLength = if (n.min > 0) this.getTrimmedMinLength*n.min else 0)
            }
          } else {
            StringAttrs.empty
          }
        }
        def *(n: Int): StringAttrs =
          if (n <= 0) {
            warn(treePosHolder, StringMultiplicationByNonPositive)
            new StringAttrs(Some(""))
          } else {
            new StringAttrs(
              exactValue = if (this.exactValue.isDefined) Some(this.exactValue.get*n) else None,
              minLength = this.getMinLength*n,
              trimmedMinLength = this.getTrimmedMinLength*n, //ADD: can be made more exact
              maxLength = if (this.getMaxLength == Int.MaxValue) Int.MaxValue else this.getMaxLength*n,
              trimmedMaxLength = if (this.getTrimmedMaxLength == Int.MaxValue) Int.MaxValue else this.getTrimmedMaxLength*n,
              prefix = this.prefix,
              suffix = this.suffix,
              knownPieces = this.knownPieces ++ (if (n >= 2) Set(this.suffix+this.prefix) else Nil))
          }

        //TODO: wait what... I hope this isn't needed anywhere :)
        override def hashCode: Int = exactValue.hashCode + name.hashCode + minLength + trimmedMinLength + maxLength + trimmedMaxLength
        override def equals(that: Any): Boolean = that match {
          case that: StringAttrs => (this.exactValue.isDefined && that.exactValue.isDefined && this.exactValue.get == that.exactValue.get)
          case that: String => this.exactValue.exists(_ == that)
          case _ => false
        }

        override def toString: String =
         "StringAttrs" + (if (exactValue.isDefined) "("+exactValue.get+")" else (name, getMinLength, getTrimmedMinLength, getMaxLength, getTrimmedMaxLength, prefix, suffix, knownPieces))
      }

      def traverseBlock(tree: Tree): Unit = {
        pushDefinitions()
        traverse(tree)
        popDefinitions()
      }

      var superTraverse = true
      def catcher(): PartialFunction[Throwable, Unit] = {
        case e: NullPointerException => //Ignore
        case e: NoSuchMethodError => //Ignore
        case e: StackOverflowError => superTraverse = false
        case e: Exception => //TODO: Print details and ask user to report it
      }
      def finalizer(tree: Tree): Unit = {
        if (superTraverse) try { super.traverse(tree) } catch catcher else doNotTraverse += tree
      }
      override def traverse(tree: Tree): Unit = try {
        superTraverse = true
        if (doNotTraverse contains tree) { superTraverse = false; return }
        treePosHolder = tree
        tree match {
          /// Very hacky support for some var interpretion
          /*case ValDef(m: Modifiers, varName, _, value) if (m.hasFlag(MUTABLE)) =>
            vars += varName.toString
            vals(varName.toString) = computeExpr(value)
            //println("assign: "+(vals))*/
          case Assign(varName, value) if vars contains varName.toString =>
            vals += varName.toString -> computeExpr(value)
            //println("reassign: "+(vals))

          case LabelDef(label, Nil, If(_, _, _)) if {
            discardVars(tree, (vars & getUsed(tree)))
            labels += label.toString -> tree
            false
          } => //Fallthrough
          case Apply(label, Nil) if (labels contains label.toString) && {
            //TODO: check if there is an infinite...
            // vals.filter(a => vars contains a._1).map(a => println(a._1, a._2.applyCond(labels(label.toString))._2))

            discardVars(labels(label.toString))//, (vars & getUsed(labels(label.toString))).toSeq:_*)

            labels -= label.toString
            false
          } => //Fallthrough
          case e if {
            // Throw away assigns inside other blocks
            discardVars(e)
            false
          } => //Fallthrough

          case forloop @ Apply(TypeApply(Select(_collection, _), _), List(Function(List(ValDef(_, _, _, _)), _))) =>
            forLoop(forloop)

          /// Assertions checks (assert, assume, require)
          case Apply(Select(scala_Predef, assertion), List(condExpr))
            if (scala_Predef.tpe.widen <:< PredefModule.tpe) && (assertion.toString matches "assert|assume|require") =>

            // We can apply these conditions to vals - if they don't hold, it'll throw an exception anyway
            // and they'll reset at the end of the current block
            vals = vals.map(a => (a._1, a._2.applyCond(condExpr)._1)).withDefaultValue(Values.empty)

          /// String checks
          //case s @ Literal(Constant(str: String)) if stringVals.filter(s => s.name.isDefined && !(vars contains s.name.get)).find(_.exactValue == Some(str)).isDefined =>
            //warn(s, "You have defined that string as a val already, maybe use that?")

          case ValDef(m: Modifiers, valName, _, str @ Literal(Constant(_: String))) if (!m.isMutable && !m.isFinal && !m.hasDefault) =>
            //if (stringVals.filter(s => s.name.isDefined && !(vars contains s.name.get)).exists(_.exactValue == Some(str)))
              //warn(s, "You have defined that string as a val already, maybe use that?")
            //stringVals += str

            val str2 = StringAttrs(str).addName(valName.toString)
            //println("str2: "+str2)
            if (str2.exactValue.isDefined || str2.getMinLength > 0) {
              stringVals += str2
            }
            //println("stringVals2: "+stringVals)

          case ValDef(m: Modifiers, valName, _, Literal(Constant(a: Int))) if (!m.isMutable && !m.hasDefault) =>
            val valNameStr = valName.toString.trim
            vals += valNameStr -> Values(a, valNameStr)
            //println(vals(valName.toString))

          case ValDef(m: Modifiers, valName, _, expr) if (!m.hasDefault) =>
            //if !m.hasFlag(MUTABLE) /*&& !m.hasFlag(LAZY)) && !computeExpr(expr).isEmpty*/ => //&& computeExpr(expr).isValue =>
            //ADD: aliasing... val a = i, where i is an iterator, then 1/i-a is divbyzero
            //ADD: isSeq and actualSize

            if (expr.tpe.widen <:< StringClass.tpe) {
              val str = StringAttrs(expr).addName(valName.toString) //StringAttrs.toStringAttrs(expr)
              //println("str1: "+str)
              if (str.exactValue.isDefined || str.getMinLength > 0) {
                //println(str)
                stringVals += str
              }
              //println("stringVals1: "+stringVals)
            }

            // private[this] var k = 4 messes up a few things
            if (!(m.isPrivateLocal && m.isMutable)) {
              val valNameStr = valName.toString
              val res = computeExpr(expr).addName(valNameStr)
              vals += valNameStr -> res
              if (m.isMutable) vars += valNameStr
            }


            //println("newVal: "+computeExpr(expr).addName(valNameStr))
            //println("newVal: "+vals(valName.toString))
            //println(showRaw(expr))

            expr match {
              case e => //Block(_, _) | If(_, _, _) =>
                //println(expr)
                pushDefinitions()

                traverse(expr)

                popDefinitions()
              //case _ =>
            }

          case Match(pat, cases) if pat.tpe.toString != "Any @unchecked" && cases.size >= 2 =>
            for (c <- cases) {
              pushDefinitions()

              //TODO: c.pat can override some variables
              traverse(c.body)

              popDefinitions()
            }

          case If(condExpr, t, f) => //TODO: moved to computeExpr?
            val backupVals = vals
            val backupStrs = stringVals

            pushDefinitions()
            super.traverse(condExpr)
            popDefinitions()

            pushDefinitions()

            //println(vals)
            vals = backupVals.map(a => (a._1, a._2.applyCond(condExpr)._1)).withDefaultValue(Values.empty)
            //println(vals)
            //ADD: if always true, or always false pass the return value, e.g. val a = 1; val b = if (a == 1) 5 else 4
            try { super.traverse(t) } catch catcher

            popDefinitions()
            pushDefinitions()

            vals = backupVals.map(a => (a._1, a._2.applyCond(condExpr)._2)).withDefaultValue(Values.empty)
            try { super.traverse(f) } catch catcher

            popDefinitions()
            //println(vals)

          /// Attempt to verify collection indices are correct
          //case pos @ Apply(Select(Ident(seq), apply), List(indexExpr))
          case pos @ Apply(Select(seq, _apply), List(indexExpr)) if methodImplements(pos.symbol, SeqLikeApply) =>
            //println(seq.toString)
            //println("indexExpr: "+computeExpr(indexExpr))
            //println(showRaw(indexExpr))
            if (vals.contains(seq.toString) && vals(seq.toString).actualSize != -1 && (computeExpr(indexExpr).existsGreater(vals(seq.toString).actualSize-1))) {
              warn(pos, LikelyIndexOutOfBounds("too large"))
            }
            if (computeExpr(indexExpr).existsLower(0)) {
              warn(pos, LikelyIndexOutOfBounds("negative"))
            }

          case DefDef(_, name, _, params, _, block @ Block(b, last)) =>
            //if you want to model one expr funcs - here's a start
            /*val (block, last) = body match {
              case block @ Block(b, last) => (block, last)
              case expr => (EmptyTree, expr)
            }*/

            pushDefinitions()

            //TODO: handle params
            val paramNames = params.flatten.map(_.name.toString)
            vals = vals.filterNot(paramNames contains _._1)
            discardVars()
            doNotTraverse ++= params.flatten
            try { super.traverse(block) } catch catcher

            val returnVal = last match {
              case Return(ret) =>
                def otherReturns: Boolean = {
                  for (Return(ret) <- b) return true
                  false
                }
                /// Unnecessary use of return keyword
                if (otherReturns) warn(last, UnnecessaryReturn)
                ret
              case a =>
                a
            }

            //defModels = backupDefModels

            /// Method always returns the same value
            if (returnCount(block) == 0 && throwsCount(block) == 0) {//ADD: can be made better - if sentences are easy to model
              val retVal = computeExpr(returnVal)
              if (retVal.isValue || (retVal.isSeq && retVal.size > 0)) {
                warn(last, InvariantReturn("method", retVal.getValue.toString))
              }
              popDefinitions()
              if (retVal.nonEmpty || retVal.conditions.nonEmpty || (retVal.isSeq && retVal.actualSize != -1)) {
                //println("ModeledV: "+name.toString+" "+retVal)
                defModels += name.toString -> Left(retVal)
              } else {
                val retVal = StringAttrs(returnVal)
                if ((retVal.getMinLength > 0 || retVal.getMaxLength < Int.MaxValue) && !(name.toString matches "[<$]init[$>]")) {
                  //println("ModeledS: "+name.toString+" "+retVal)
                  defModels += name.toString -> Right(retVal)
                }
              }
            } else {
              popDefinitions()
            }

          /// Invalid regex (runtime exception)
          case Apply(java_util_regex_Pattern_compile, List(regExpr)) if java_util_regex_Pattern_compile.toString == "java.util.regex.Pattern.compile" =>
            treePosHolder = regExpr
            StringAttrs(regExpr).exactValue.foreach(checkRegex)

          case Apply(Select(str, func), List(regExpr)) if (str.tpe.widen <:< StringClass.tpe) && (func.toString matches "matches|split") =>
            treePosHolder = regExpr
            StringAttrs(regExpr).exactValue.foreach(checkRegex)
            if (func.toString == "matches") computeExpr(tree)

          case Apply(Select(str, func), List(regExpr, _str)) if (str.tpe.widen <:< StringClass.tpe) && (func.toString matches "replace(All|First)") =>
            treePosHolder = regExpr
            StringAttrs(regExpr).exactValue.foreach(checkRegex)
            computeExpr(tree)

          case Select(Apply(scala_Predef_augmentString, List(regExpr)), r)
            if (scala_Predef_augmentString.toString.endsWith(".augmentString") && r.toString == "r") =>
            treePosHolder = regExpr
            StringAttrs(regExpr).exactValue.foreach(checkRegex)

          /// Some format string passing
          case Apply(formatFunc, params)
            if ((formatFunc.toString endsWith "Predef.printf")
            || (formatFunc.toString endsWith "Console.printf")
            || (formatFunc.toString endsWith "lang.String.format"))
            && (params.size >= 2) && (params(1) match {
              case Typed(_, Ident(tpnme.WILDCARD_STAR)) => false
              case _ => true
            }) =>

            StringAttrs.stringFunc(params.head, newTermName("format"), params.tail)

          /// Checks conditions that use Option.size (there is a separate check for all uses of Option.size)
          //ADD: Generalize... move to applyCond completely, make it less hacky
          case optCond @ Apply(Select(Select(Apply(option2Iterable, List(_option)), size), op), List(expr))
            if (option2Iterable.toString contains "Option.option2Iterable")
            && size.toString == "size"
            && optCond.tpe.widen <:< BooleanClass.tpe =>

            pushDefinitions()

            val valName = "__foobar__" //TODO: shoot me :P

            vals += valName -> (new Values(values = Set(0, 1)))

            val cond = Apply(Select(Ident(newTermName(valName)), op), List(expr))
            cond.pos = optCond.pos

            vals(valName).applyCond(cond)

            popDefinitions()

          case Block(_stmts, _ret) =>
            //println("block: "+b)
            pushDefinitions()

            try { super.traverse(tree) } catch catcher

            /// Try to use vars - TODO: is probably buggy
            /*val block = stmts :+ ret
            val vars = mutable.HashSet[String]()
            block foreach {
              case ValDef(m: Modifiers, varName, _, value) if (m.hasFlag(MUTABLE)) =>
                vars += varName.toString
                vals(varName.toString) = computeExpr(value)
                println("assign: "+(vals))
              case Assign(varName, value) if vars contains varName.toString =>
                vals(varName.toString) = computeExpr(value)
                println("reassign: "+(vals))
              case e =>
                // Throw away assigns inside other blocks
                for (v <- vars; if isAssigned(e, v)) {
                  vals(v) = Values.empty
                  println("discard: "+(vals))
                }
            }
            */

            popDefinitions()

          /// Pass on expressions
          case a =>
            //TODO: Range and Lists work too, I think
            if (a.tpe != null && ((a.tpe <:< StringClass.tpe) || (a.tpe <:< AnyValClass.tpe))) computeExpr(a)

            //if (vals.nonEmpty)println("in: "+showRaw(tree))
            //if (vals.nonEmpty)println(">   "+vals);
            //if (showRaw(tree).startsWith("Literal") || showRaw(tree).startsWith("Constant"))println("in: "+showRaw(tree))
            //tree.children.foreach(traverse)
            try { super.traverse(tree) } catch catcher
        }
        //super.traverse(tree)
      } catch catcher
    }
  }

  private[this] object PostRefChecksComponent extends PluginComponent {
    val global = LinterPlugin.this.global
    import global._

    override val runsAfter = List("refchecks")

    val phaseName = "linter-refchecked"

    override def newPhase(prev: Phase): StdPhase = new StdPhase(prev) {
      override def apply(unit: global.CompilationUnit): Unit = {
        nowarnPositions.clear
        if (!unit.isJava) new PostRefChecksTraverser(unit).traverse(unit.body)
      }
    }

    class PostRefChecksTraverser(unit: CompilationUnit) extends Traverser {
      implicit val unitt: CompilationUnit = unit

      var superTraverse = true
      def catcher(): PartialFunction[Throwable, Unit] = {
        case e: NullPointerException => //Ignore
        case e: NoSuchMethodError => //Ignore
        case e: StackOverflowError => superTraverse = false
        case e: Exception => //TODO: Print details and ask user to report it
      }
      def finalizer(tree: Tree): Unit = {
        if (superTraverse) try { super.traverse(tree) } catch catcher
      }
      override def traverse(tree: Tree): Unit = try {
        superTraverse = true
        tree match {
          /// Unused method parameters
          case DefDef(mods: Modifiers, name, _, valDefs, _, body)
            //TODO: write tests for the special cases - isSynthetic might cover some
            //TODO: scalaz is a good codebase for finding interesting false positives
            //TODO: macro impl is special case?
            if (name.toString != "" && !name.toString.contains("$default$"))
            && !body.isEmpty && body.toString != staticSelect("scala", "Predef.???")
            && !mods.isSynthetic && !(mods.isOverride || tree.symbol.isOverridingSymbol) =>

            // Get the parameters, except the implicit/synthetic ones
            val params =
              valDefs
                .flatMap(_.filterNot(valDef => valDef.mods.isImplicit || valDef.mods.isSynthetic || valDef.name.toString == "$this"))
                .map(_.name.toString)
                .toBuffer

            if (!(name.toString == "main" && params.size == 1 && params.head == "args")) { // Filter main method
              val used = for (Ident(name) <- tree if params contains name.toString) yield name.toString
              val unused = params -- used

              if (unused.size > 0) {
                warn(tree, UnusedParameter(unused, name.toString.stripSuffix("$extension")))
              }
            }

          case _ =>
        }
      } catch catcher finally finalizer(tree)
    }
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy