Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inline scalafix #5229

Merged
merged 1 commit into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 0 additions & 85 deletions scalafix/rules/src/main/scala/fix/common/Common.scala

This file was deleted.

87 changes: 81 additions & 6 deletions scalafix/rules/src/main/scala/fix/v0_14_0/FixAvroCoder.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package fix.v0_14_0

import fix.common.Common
import scalafix.v1._

import scala.meta._
Expand All @@ -22,8 +21,86 @@ object FixAvroCoder {
"org/apache/avro/generic/GenericRecord"
)

/** @return true if `sym` is a class whose parents include a type matching `parentMatcher` */
def hasParentClass(sym: Symbol, parentMatcher: SymbolMatcher)(implicit
sd: SemanticDocument
): Boolean = {
sym.info.get.signature match {
case ClassSignature(_, parents, _, _) =>
parents
.collect {
case TypeRef(_, p, _) if parentMatcher.matches(p) => true
}
.foldLeft(false)(_ || _)
case _ => false
}
}

/**
* Match a variable declaration, e.g. with `t @ q"..$mods val ..$patsnel: $tpeopt = $expr"` and
* pass `t` to this function.
*
* @return
* The type parameters to the type of `t` if it matches `matcher`
*/
def findMatchingValTypeParams(
t: Tree,
matcher: SymbolMatcher
)(implicit doc: SemanticDocument): List[Symbol] = {
// get the type of the declaration ...
t.symbol.info.get.signature match {
// ... and if it matches, collect all of its type parameters
case MethodSignature(_, _, TypeRef(_, symbol, typeArgs)) if matcher.matches(symbol) =>
typeArgs.collect { case TypeRef(_, param, _) => param }
case _ => List.empty
}
}

/**
* Match a type application with q"$expr[..$tpesnel]".
*
* Given some function call with a context bound e.g.
* - `func[T : ContextBoundType]` or
* - `func[T](cbt: ContextBoundType[T])` or
* - `func[T](..)(implicit cbt:ContextBoundType[T])` or
* - `Class[T : ContextBoundType]`
*
* @return
* The symbols in `tpesnel` which are a type param to the context bound type
*/
def findBoundedTypes(
expr: Term,
tpesnel: Seq[Type],
contextBoundMatcher: SymbolMatcher
)(implicit
doc: SemanticDocument
): List[Symbol] = {
expr.symbol.info.get.signature match {
case MethodSignature(typeParams, parameterLists, _) =>
// find only the types for which the context bound type is required
val contextBoundTypeParameters = parameterLists.flatten
.map(_.signature)
.collect {
case ValueSignature(TypeRef(_, symbol, args)) if contextBoundMatcher.matches(symbol) =>
args.collect { case TypeRef(_, schemaSymbol, _) => schemaSymbol }
}
.flatten

// join the actual params with the positional type params
// and filter for the ones for which the context bound is required
tpesnel
.zip(typeParams)
.collect {
case (tpe, tParam) if contextBoundTypeParameters.contains(tParam.symbol) =>
tpe.symbol
}
.toList
case _ => List.empty
}
}

def isAvroType(sym: Symbol)(implicit sd: SemanticDocument): Boolean =
AvroMatcher.matches(sym) || Common.hasParentClass(sym, AvroMatcher)
AvroMatcher.matches(sym) || hasParentClass(sym, AvroMatcher)
}

class FixAvroCoder extends SemanticRule("FixAvroCoder") {
Expand All @@ -34,13 +111,11 @@ class FixAvroCoder extends SemanticRule("FixAvroCoder") {
.collect {
case q"$expr[..$tpesnel]" =>
// A method call with a type parameter requires an implicit Coder[T] for our type
Common
.findBoundedTypes(expr, tpesnel, CoderMatcher)
findBoundedTypes(expr, tpesnel, CoderMatcher)
.exists(isAvroType)
case t @ q"..$mods val ..$patsnel: $tpeopt = $expr" =>
// Coder[T] is a variable type where T is an avro type
Common
.findMatchingValTypeParams(t, CoderMatcher)
findMatchingValTypeParams(t, CoderMatcher)
.exists(isAvroType)
}
.foldLeft(false)(_ || _)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package fix.v0_14_0

import fix.common.Common
import scalafix.v1._

import scala.meta._
Expand All @@ -17,8 +16,86 @@ object FixAvroSchemasPackage {
"org/apache/avro/specific/SpecificRecord"
)

/** @return true if `sym` is a class whose parents include a type matching `parentMatcher` */
def hasParentClass(sym: Symbol, parentMatcher: SymbolMatcher)(implicit
sd: SemanticDocument
): Boolean = {
sym.info.get.signature match {
case ClassSignature(_, parents, _, _) =>
parents
.collect {
case TypeRef(_, p, _) if parentMatcher.matches(p) => true
}
.foldLeft(false)(_ || _)
case _ => false
}
}

/**
* Match a variable declaration, e.g. with `t @ q"..$mods val ..$patsnel: $tpeopt = $expr"` and
* pass `t` to this function.
*
* @return
* The type parameters to the type of `t` if it matches `matcher`
*/
def findMatchingValTypeParams(
t: Tree,
matcher: SymbolMatcher
)(implicit doc: SemanticDocument): List[Symbol] = {
// get the type of the declaration ...
t.symbol.info.get.signature match {
// ... and if it matches, collect all of its type parameters
case MethodSignature(_, _, TypeRef(_, symbol, typeArgs)) if matcher.matches(symbol) =>
typeArgs.collect { case TypeRef(_, param, _) => param }
case _ => List.empty
}
}

/**
* Match a type application with q"$expr[..$tpesnel]".
*
* Given some function call with a context bound e.g.
* - `func[T : ContextBoundType]` or
* - `func[T](cbt: ContextBoundType[T])` or
* - `func[T](..)(implicit cbt:ContextBoundType[T])` or
* - `Class[T : ContextBoundType]`
*
* @return
* The symbols in `tpesnel` which are a type param to the context bound type
*/
def findBoundedTypes(
expr: Term,
tpesnel: Seq[Type],
contextBoundMatcher: SymbolMatcher
)(implicit
doc: SemanticDocument
): List[Symbol] = {
expr.symbol.info.get.signature match {
case MethodSignature(typeParams, parameterLists, _) =>
// find only the types for which the context bound type is required
val contextBoundTypeParameters = parameterLists.flatten
.map(_.signature)
.collect {
case ValueSignature(TypeRef(_, symbol, args)) if contextBoundMatcher.matches(symbol) =>
args.collect { case TypeRef(_, schemaSymbol, _) => schemaSymbol }
}
.flatten

// join the actual params with the positional type params
// and filter for the ones for which the context bound is required
tpesnel
.zip(typeParams)
.collect {
case (tpe, tParam) if contextBoundTypeParameters.contains(tParam.symbol) =>
tpe.symbol
}
.toList
case _ => List.empty
}
}

def isSpecificRecord(sym: Symbol)(implicit sd: SemanticDocument) =
Common.hasParentClass(sym, SpecificRecordMatcher)
hasParentClass(sym, SpecificRecordMatcher)

def avroImportee(imps: Seq[Importee]): Patch = {
imps.collect { case i @ importee"fromAvroSchema" =>
Expand All @@ -35,13 +112,11 @@ class FixAvroSchemasPackage extends SemanticRule("FixAvroSchemasPackage") {
.collect {
case q"$expr[..$tpesnel]" =>
// A method call with a type parameter requires an implicit Schema[T] for our type
Common
.findBoundedTypes(expr, tpesnel, SchemaMatcher)
findBoundedTypes(expr, tpesnel, SchemaMatcher)
.exists(isSpecificRecord)
case t @ q"..$mods val ..$patsnel: $tpeopt = $expr" =>
// Schema[T] is a variable type where T <: SpecificRecord
Common
.findMatchingValTypeParams(t, SchemaMatcher)
findMatchingValTypeParams(t, SchemaMatcher)
.exists(isSpecificRecord)
}
.foldLeft(false)(_ || _)
Expand Down
Loading