Skip to content

Commit

Permalink
some impr
Browse files Browse the repository at this point in the history
  • Loading branch information
halotukozak committed Oct 2, 2024
1 parent 44e36aa commit 595a880
Show file tree
Hide file tree
Showing 11 changed files with 131 additions and 109 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import dotty.tools.dotc.reporting.Diagnostic
import dotty.tools.dotc.util.NoSourcePosition

final class AnalyzerPlugin extends StandardPlugin:
plugin =>

private lazy val rules = List(
new ImportJavaUtil,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ import dotty.tools.dotc.transform.{Pickler, Staging}
import dotty.tools.dotc.util.SourcePosition

import scala.compiletime.uninitialized
import scala.util.ChainingSyntax

abstract class AnalyzerRule(val name: String, defaultLevel: Level = Level.Warn) extends PluginPhase:
abstract class AnalyzerRule(val name: String, defaultLevel: Level = Level.Warn) extends PluginPhase, ChainingSyntax:

import tpd.*

Expand All @@ -28,11 +29,11 @@ abstract class AnalyzerRule(val name: String, defaultLevel: Level = Level.Warn)
report(message, tree.symbol)(using tree.sourcePos)

protected final def report(
message: String,
site: Symbol = NoSymbol
message: String,
site: Symbol = NoSymbol
)(using
position: SourcePosition,
ctx: Context
position: SourcePosition,
ctx: Context
): Unit = ctx.reporter.report {
level match
case Level.Off =>
Expand Down Expand Up @@ -60,6 +61,6 @@ object Level:
case '-' => Level.Off
case '*' => Level.Info
case '+' => Level.Error
case _ => Level.Warn
case _ => Level.Warn

end Level
Original file line number Diff line number Diff line change
@@ -1,54 +1,33 @@
package com.avsystem.commons
package analyzer

import dotty.tools.dotc.ast.tpd
import dotty.tools.dotc.ast.tpd.*
import dotty.tools.dotc.core.Contexts.Context
import dotty.tools.dotc.core.{Contexts, Symbols}
import dotty.tools.dotc.transform.PickleQuotes

final class ExplicitGenerics extends AnalyzerRule("explicitGenerics"):
import ExplicitGenerics.*

import tpd.*

// lazy val explicitGenericsAnnotTpe = classType("com.avsystem.commons.annotation.explicitGenerics")

// def analyze(unit: CompilationUnit) = if (explicitGenericsAnnotTpe != NoType) {
// def requiresExplicitGenerics(sym: Symbol): Boolean =
// sym != NoSymbol && (sym :: sym.overrides).flatMap(_.annotations).exists(_.tree.tpe <:< explicitGenericsAnnotTpe)
//
// def analyzeTree(tree: Tree): Unit = analyzer.macroExpandee(tree) match {
// case `tree` | EmptyTree =>
// tree match {
// case t@TypeApply(pre, args) if requiresExplicitGenerics(pre.symbol) =>
// val inferredTypeParams = args.forall {
// case tt: TypeTree => tt.original == null || tt.original == EmptyTree
// case _ => false
// }
// if (inferredTypeParams) {
// report(t.pos, s"${pre.symbol} requires that its type arguments are explicit (not inferred)")
// }
// case _ =>
// }
// tree.children.foreach(analyzeTree)
// case prevTree =>
// analyzeTree(prevTree)
// }
// analyzeTree(unit.body)
// }

override def transformTypeApply(tree: TypeApply)(using Context): Tree =
val TypeApply(fun: Tree, args) = tree
val explicitGenerics = Symbols.requiredClass("com.avsystem.commons.annotation.explicitGenerics")
if fun.symbol.hasAnnotation(explicitGenerics) then
args.foreach {
case tt: TypeTree /*if tt.original == null || tt.original == EmptyTree */ =>
report(s"${fun.symbol} requires that its type arguments are explicit (not inferred)", tree)
case _ =>
}

end if
tree

end transformTypeApply
override def transformTypeApply(tree: TypeApply)(using Context): Tree = tree.tap {
case TypeApply(fun: Tree, args)
if fun.symbol.hasAnnotation(explicitGenericsSymbol) && args.containsInferredTypeParameters =>
report(s"${fun.symbol} requires that its type arguments are explicit (not inferred)", tree)
case _ =>
}

override def transformInlined(tree: Inlined)(using Context): Tree = tree.tap {
case Inlined(Apply(typeApply: TypeApply, _), _, _) => transformTypeApply(typeApply)
case _ =>
}

end ExplicitGenerics

private object ExplicitGenerics:

private final val explicitGenericsSymbol =
(ctx: Context) ?=> Symbols.requiredClass("com.avsystem.commons.annotation.explicitGenerics")

extension (args: List[Tree])
private def containsInferredTypeParameters: Boolean = args.exists(_.isInstanceOf[TypeTree])

end ExplicitGenerics
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,30 @@ import dotty.tools.dotc.ast.{Trees, tpd}
import dotty.tools.dotc.core.Contexts.Context
import dotty.tools.dotc.core.Symbols
import dotty.tools.dotc.core.Symbols.*
import tpd.*
import ImportJavaUtil.*

final class ImportJavaUtil extends AnalyzerRule("importJavaUtil"):

import tpd.*

override def transformOther(tree: Tree)(using Context): Tree =
val javaPkgName = Symbols.requiredPackageRef("java").name
val javaUtilPkgName = Symbols.requiredPackageRef("java.util").name
tree match
case Import(Ident(`javaPkgName`), selectors: List[ImportSelector]) if selectors.exists {
case ImportSelector(Ident(`javaUtilPkgName`), EmptyTree, _) => true
case _ => false
} => report(
override def transformOther(tree: Tree)(using Context): Tree = tree.tap {
case Import(Ident(`javaPkgName`), selectors: List[ImportSelector]) if selectors.exists {
case ImportSelector(Ident(`javaUtilPkgName`), EmptyTree, _) => true
case _ => false
} =>
report(
"Don't import java.util: either import with rename (e.g. import java.{util => ju}) " +
"or use type aliases from JavaInterop (e.g. JList, JSet, etc)",
tree,
tree
)
case _ =>
case _ =>
}

end match
end transformOther

tree
end ImportJavaUtil

end transformOther
private object ImportJavaUtil:
private final val javaPkgName = (ctx: Context) ?=> Symbols.requiredPackageRef("java").name
private final val javaUtilPkgName = (ctx: Context) ?=> Symbols.requiredPackageRef("java.util").name

end ImportJavaUtil
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
package com.avsystem.commons
package analyzer

import com.avsystem.commons.analyzer.{AnalyzerRule, ExplicitGenerics}
import com.avsystem.commons.analyzer.ExplicitGenerics.explicitGenericsSymbol
import com.avsystem.commons.analyzer.VarargsAtLeast.atLeastSymbol
import dotty.tools.dotc.ast.tpd.*
import dotty.tools.dotc.core.Constants.Constant
import dotty.tools.dotc.core.Contexts.Context
import dotty.tools.dotc.core.Symbols
//package com.avsystem.commons
//package analyzer
//
Expand Down Expand Up @@ -37,3 +47,50 @@
// })
//
//end VarargsAtLeast

final class VarargsAtLeast extends AnalyzerRule("varargsAtLeast"):

override def transformApply(tree: Apply)(using Context): Tree = tree.tap {
case Apply(fun: Tree, argss: List[Tree]) =>
val paramss = fun.symbol.denot.paramSymss

paramss.zip(argss).foreach { case (params, args) =>
// params.zip(args).foreach { case (param, arg) =>
// println("dupa")
// }

// param.getAnnotation(atLeastSymbol).flatMap(_.argumentConstant(0)).map(_.intValue).foreach { required =>

println("dupa")
}

// if param.isRepeatedParam && !args.lastOption.exists {
// case Typed(_, Ident(tpnme.WILDCARD_STAR)) => true
// case _ => false
// } then
// val required = param.annotations
// .find(_.tree.tpe <:< atLeastAnnotTpe)
// .map(_.tree.children.tail)
// .collect { case List(Literal(Constant(n: Int))) =>
// n
// }
// .getOrElse(0)
//
// val actual = args.size - params.size + 1
//
// if actual < required then
// report(
// s"This method requires at least $required arguments for its repeated parameter, $actual passed.",
// tree
// )
case _ =>
}

end VarargsAtLeast

object VarargsAtLeast:

private final val atLeastSymbol =
(ctx: Context) ?=> Symbols.requiredClass("com.avsystem.commons.annotation.atLeast")

end VarargsAtLeast
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import org.scalatest.Assertions
import scala.util.chaining.scalaUtilChainingOps

trait AnalyzerTest:
analyzer: Assertions =>
this: Assertions =>

lazy val compiler = new Compiler

Expand Down Expand Up @@ -47,7 +47,7 @@ trait AnalyzerTest:

base.initialCtx.fresh.tap { ctx =>
ctx.setSetting(ctx.settings.usejavacp, true)
ctx.setSetting(ctx.settings.pluginOptions, "AVSystemAnalyzer:+_" :: analyzer.pluginOptions)
ctx.setSetting(ctx.settings.pluginOptions, "AVSystemAnalyzer:+_" :: this.pluginOptions)
base.initialize()(using ctx)
}

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,5 @@ final class ImportJavaUtilTest extends AnyFunSuite with AnalyzerTest:
""".stripMargin
)
}

end ImportJavaUtilTest
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
package com.avsystem.commons
package analyzer

import com.avsystem.commons.annotation.explicitGenerics
import com.avsystem.commons.annotation.{atLeast, explicitGenerics}

import scala.quoted.{Expr, Quotes}

object TestUtils:
// def need3Params(@atLeast(3) args: Any*) = ()
def need3Params(@atLeast(3) args: Any*)(other: List[Int]) = ()
//
// @macroPrivate
// def macroPrivateMethod = 42
def genericMacroImpl[T](arg: Expr[T])(using Quotes): Expr[T] = arg

@explicitGenerics
def genericMethod[T](arg: T): T = arg

// def invokeMacroPrivateMethod: Int = macro invokeMacroPrivateMethodImpl
// def invokeMacroPrivateMethodImpl(c: blackbox.Context): c.Tree =
// import c.universe.*
Expand All @@ -24,6 +26,7 @@ object TestUtils:
// object Extractor:
// @macroPrivate def unapply(any: Any): Option[Any] = None
//

// end Extractor

end TestUtils
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ final class VarargsAtLeastTest extends AnyFunSuite with AnalyzerTest:
"""
|import com.avsystem.commons.analyzer.TestUtils
|
|object T
|object whatever {
| TestUtils.need3Params(1, 2)
| TestUtils.need3Params(1, 2, "String", T)(List(8, 9))
|}
""".stripMargin
)
Expand Down
18 changes: 18 additions & 0 deletions core3/src/main/scala/com/avsystem/commons/annotation/atLeast.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.avsystem.commons
package annotation

import scala.annotation.StaticAnnotation

/**
* When applied on varargs parameter, indicates that at least some number of parameters is required.
* This is later checked by the static analyzer.
* <br/>
* WARNING: implementation of method which takes a varargs parameter may NOT assume that given number of
* arguments will always be passed, because it's still possible to pass a `Seq` where
* varargs parameter is required using the `: _*` ascription, e.g.
* {{{
* varargsMethod(List(): _*)
* }}}
* and that is not checked by the static analyzer.
*/
final class atLeast(n: Int) extends StaticAnnotation

0 comments on commit 595a880

Please sign in to comment.