Skip to content

Commit

Permalink
Merge pull request #210 from VirtusLab/sorting
Browse files Browse the repository at this point in the history
Sorting
  • Loading branch information
KacperFKorban authored Aug 26, 2021
2 parents d23d9b4 + a8b819d commit 05475e6
Show file tree
Hide file tree
Showing 20 changed files with 423 additions and 124 deletions.
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ ThisBuild / developers := List(
Developer(
"pikinier20",
"Filip Zybała",
"fzybala@virtuslab.com",
"filip.zybala@gmail.com",
url("https://twitter.com/pikinier20")
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@ package org.virtuslab.inkuire.engine.common.model
import cats.data.StateT
import cats.effect.IO
import org.virtuslab.inkuire.engine.common.parser.BaseSignatureParserService
import org.virtuslab.inkuire.engine.common.service.{BaseMatchService, BaseSignatureResolver, SignaturePrettifier}
import org.virtuslab.inkuire.engine.common.service._

object Engine {
case class Env(
db: InkuireDb,
matcher: BaseMatchService,
prettifier: SignaturePrettifier,
parser: BaseSignatureParserService,
resolver: BaseSignatureResolver,
appConfig: AppConfig
db: InkuireDb,
matcher: BaseMatchService,
matchQualityService: BaseMatchQualityService,
prettifier: SignaturePrettifier,
parser: BaseSignatureParserService,
resolver: BaseSignatureResolver,
appConfig: AppConfig
)

type Engine[A] = StateT[IO, Env, A]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ case class Match(
functionName: String,
packageLocation: String,
pageLocation: String,
entryType: String
entryType: String,
mq: Int
)

sealed trait OutputFormat
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ case class AncestryGraph(
nodes: Map[ITID, (Type, Seq[Type])],
implicitConversions: Seq[(TypeLike, Type)],
typeAliases: Map[ITID, TypeLike]
) extends VarianceOps {
) extends MatchingOps {

val cacheNeg: MSet[(TypeLike, TypeLike)] = MSet.empty

Expand Down Expand Up @@ -131,58 +131,6 @@ case class AncestryGraph(
}
}

def dealias(concreteType: Type, node: TypeLike): Option[TypeLike] = (concreteType, node) match {
case (t: Type, rhs: Type) if !t.isGeneric =>
Some(rhs)
case (t: Type, rhs: TypeLambda) if t.params.size == rhs.args.size =>
Some(substituteBindings(rhs.result, rhs.args.flatMap(_.itid).zip(t.params.map(_.typ)).toMap))
case _ =>
None
}

private def specializeParents(concreteType: Type, node: (Type, Seq[TypeLike])): Seq[TypeLike] = {
val (declaration, parents) = node
def resITID(t: TypeLike): Option[ITID] = t match {
case t: Type => t.itid
case t: TypeLambda => resITID(t.result)
case _ => None
}
val bindings: Map[ITID, TypeLike] =
declaration.params
.map(_.typ)
.map(resITID)
.flatMap(identity)
.zip(concreteType.params.map(_.typ))
.toMap
parents.map(substituteBindings(_, bindings))
}

def substituteBindings(parent: TypeLike, bindings: Map[ITID, TypeLike]): TypeLike = parent match {
case t: Type if t.isVariable =>
t.itid match {
case None => t.modify(_.params.each.typ).using(substituteBindings(_, bindings))
case Some(itid) => bindings.get(itid).getOrElse(t)
}
case t: Type =>
t.modify(_.params.each.typ).using(substituteBindings(_, bindings))
case t: OrType =>
t.modifyAll(_.left, _.right).using(substituteBindings(_, bindings))
case t: AndType =>
t.modifyAll(_.left, _.right).using(substituteBindings(_, bindings))
case t: TypeLambda =>
t.modify(_.result).using(substituteBindings(_, bindings))
}

private def genDummyTypes(n: Int) =
1.to(n).map { i =>
val name = s"dummy$i${Random.nextString(10)}"
Type(
name = TypeName(name),
itid = Some(ITID(name, isParsed = false)),
isVariable = true
)
}

def getAllParentsITIDs(tpe: Type): Seq[ITID] = {
tpe.itid.get +: nodes.get(tpe.itid.get).toSeq.flatMap(_._2).flatMap(getAllParentsITIDs(_))
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.virtuslab.inkuire.engine.common.service

import org.virtuslab.inkuire.engine.common.model._

trait BaseMatchQualityService {

def sortMatches(functions: Seq[(ExternalSignature, Signature)]): Seq[(ExternalSignature, Int)] =
functions
.map {
case (fun, matching) => fun -> matchQualityMetric(fun, matching)
}
.sortBy(_._2)

def matchQualityMetric(externalSignature: ExternalSignature, matching: Signature): Int

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ import org.virtuslab.inkuire.engine.common.model.{ExternalSignature, InkuireDb,

trait BaseMatchService {
def inkuireDb: InkuireDb
def findMatches(resolveResult: ResolveResult): Seq[ExternalSignature]
def isMatch(resolveResult: ResolveResult)(against: ExternalSignature): Boolean
def findMatches(resolveResult: ResolveResult): Seq[(ExternalSignature, Signature)]
def isMatch(resolveResult: ResolveResult)(against: ExternalSignature): Option[Signature]
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,16 @@ import org.virtuslab.inkuire.engine.common.model.{ExternalSignature, InkuireDb,

class ExactMatchService(val inkuireDb: InkuireDb) extends BaseMatchService {

override def findMatches(resolveResult: ResolveResult): Seq[ExternalSignature] = {
inkuireDb.functions.filter(isMatch(resolveResult))
override def findMatches(resolveResult: ResolveResult): Seq[(ExternalSignature, Signature)] = {
inkuireDb.functions
.map(fun => fun -> isMatch(resolveResult)(fun))
.collect {
case (fun, Some(matching)) => fun -> matching
}
}

override def isMatch(resolveResult: ResolveResult)(against: ExternalSignature): Boolean =
resolveResult.signatures.contains(against.signature)
override def isMatch(resolveResult: ResolveResult)(against: ExternalSignature): Option[Signature] =
resolveResult.signatures.collectFirst {
case s if s == against.signature => s
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import cats.implicits._
import scala.util.Random
import scala.util.chaining._

class FluffMatchService(val inkuireDb: InkuireDb) extends BaseMatchService with VarianceOps {
class FluffMatchService(val inkuireDb: InkuireDb) extends BaseMatchService with MatchingOps {

val ancestryGraph: AncestryGraph =
AncestryGraph(inkuireDb.types, inkuireDb.implicitConversions, inkuireDb.typeAliases)
Expand All @@ -31,11 +31,13 @@ class FluffMatchService(val inkuireDb: InkuireDb) extends BaseMatchService with
}
}

override def isMatch(resolveResult: ResolveResult)(against: ExternalSignature): Boolean = {
resolveResult.signatures.exists(against.signature.canSubstituteFor(_))
override def isMatch(resolveResult: ResolveResult)(against: ExternalSignature): Option[Signature] = {
resolveResult.signatures.collectFirst {
case s if against.signature.canSubstituteFor(s) => s
}
}

override def findMatches(resolveResult: ResolveResult): Seq[ExternalSignature] = {
override def findMatches(resolveResult: ResolveResult): Seq[(ExternalSignature, Signature)] = {
val actualSignatures = resolveResult.signatures.foldLeft(resolveResult.signatures) {
case (acc, against) =>
acc.filter { sgn =>
Expand All @@ -46,8 +48,11 @@ class FluffMatchService(val inkuireDb: InkuireDb) extends BaseMatchService with
val actualResolveResult = resolveResult.modify(_.signatures).setTo(actualSignatures)
inkuireDb.functions
.filter(fun => Some(fun.signature.typesWithVariances.size) == actualSignaturesSize)
.filter(isMatch(actualResolveResult))
.distinctBy(_.uuid)
.map(fun => fun -> isMatch(actualResolveResult)(fun))
.collect {
case (fun, Some(matching)) => fun -> matching
}
.distinctBy(_._1.uuid)
}

private def checkBindings(bindings: VariableBindings): Boolean = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package org.virtuslab.inkuire.engine.common.service

import org.virtuslab.inkuire.engine.common.model._

class IsomorphismMatchQualityService(val db: InkuireDb) extends BaseMatchQualityService with MatchingOps {

def matchQualityMetric(externalSignature: ExternalSignature, matching: Signature): Int =
variancesMatchQualityMetric(
externalSignature.signature.typesWithVariances,
matching.typesWithVariances
)

def variancesMatchQualityMetric(typVariances: Seq[Variance], suprVariances: Seq[Variance]): Int =
typVariances.zip(suprVariances).map { case (v1, v2) => varianceMatchQualityMetric(v1, v2) }.sum

def varianceMatchQualityMetric(typVariance: Variance, suprVariance: Variance): Int =
(typVariance, suprVariance) match {
case (Covariance(typParam), Covariance(suprParam)) =>
typeMatchQualityMetric(typParam, suprParam)
case (Contravariance(typParam), Contravariance(suprParam)) =>
typeMatchQualityMetric(suprParam, typParam)
case (Invariance(typParam), Invariance(suprParam)) =>
typeMatchQualityMetric(typParam, suprParam) + typeMatchQualityMetric(suprParam, typParam)
case (v1, v2) => // Treating not matching variances as invariant
val typParam = v1.typ
val suprParam = v2.typ
typeMatchQualityMetric(typParam, suprParam) + typeMatchQualityMetric(suprParam, typParam)
}

/** Classes thet generally mean loss of some information */
final val avoid = Set(
"Any",
"Object",
"AnyVal",
"AnyRef",
"Matchable"
).map(TypeName.apply)

/** Matching constants */
final val aLotCost = 1000000
final val losingInformationCost = 10000
final val losingVarInformationCost = 3000
final val varToConcreteCost = 200
final val concreteToVarCost = 5000
final val andOrOrTypeCost = 50
final val dealiasCost = 2
final val subTypeCost = 4
final val typeLambdaCost = 1
final val varToVarCost = 1

val p = new ScalaExternalSignaturePrettifier

/** Returns match quality metric for two typelikes
* the lower the metric value, the better the match
*/
def typeMatchQualityMetric(typ: TypeLike, supr: TypeLike): Int = {
(typ, supr) match {
case (t: Type, s: Type) if t.isStarProjection && s.isStarProjection =>
varToVarCost
case (t: Type, _) if t.isStarProjection =>
varToConcreteCost
case (_, s: Type) if s.isStarProjection =>
concreteToVarCost
case (AndType(left, right), supr) =>
andOrOrTypeCost + (typeMatchQualityMetric(left, supr) min typeMatchQualityMetric(right, supr))
case (typ, AndType(left, right)) =>
andOrOrTypeCost + (typeMatchQualityMetric(typ, left) min typeMatchQualityMetric(typ, right))
case (OrType(left, right), supr) =>
andOrOrTypeCost + (typeMatchQualityMetric(left, supr) min typeMatchQualityMetric(right, supr))
case (typ, OrType(left, right)) =>
andOrOrTypeCost + (typeMatchQualityMetric(typ, left) min typeMatchQualityMetric(typ, right))
case (typ: TypeLambda, supr: TypeLambda) =>
val dummyTypes = genDummyTypes(typ.args.size)
val typResult = substituteBindings(typ.result, typ.args.flatMap(_.itid).zip(dummyTypes).toMap)
val suprResult = substituteBindings(supr.result, supr.args.flatMap(_.itid).zip(dummyTypes).toMap)
typeLambdaCost + typeMatchQualityMetric(typResult, suprResult)
case (_: TypeLambda, _) =>
aLotCost
case (_, _: TypeLambda) =>
aLotCost
case (typ: Type, supr: Type) if typ.isVariable && typ.isGeneric && supr.isVariable && supr.isGeneric =>
varToVarCost + variancesMatchQualityMetric(typ.params, supr.params)
case (typ: Type, supr: Type) if typ.isVariable && typ.isGeneric && supr.isGeneric =>
varToConcreteCost + variancesMatchQualityMetric(typ.params, supr.params)
case (typ: Type, supr: Type) if supr.isVariable && supr.isGeneric && typ.isGeneric =>
concreteToVarCost + variancesMatchQualityMetric(typ.params, supr.params)
case (typ: Type, supr: Type) if typ.isVariable && typ.isGeneric =>
losingVarInformationCost
case (typ: Type, supr: Type) if supr.isVariable && supr.isGeneric =>
losingVarInformationCost
case (typ: Type, supr: Type) if typ.isVariable && supr.isVariable =>
varToVarCost
case (typ: Type, supr: Type) if typ.isVariable && supr.isGeneric =>
losingVarInformationCost
case (typ: Type, supr: Type) if supr.isVariable && typ.isGeneric =>
losingVarInformationCost
case (typ: Type, supr: Type) if supr.isVariable =>
concreteToVarCost
case (typ: Type, supr: Type) if typ.isVariable =>
varToConcreteCost
case (typ: Type, supr: Type) if typ.itid == supr.itid =>
variancesMatchQualityMetric(typ.params, supr.params)
case (typ: Type, supr: Type) =>
db.typeAliases
.get(typ.itid.get)
.toList
.flatMap(alias => dealias(typ, alias))
.map((_, supr, dealiasCost))
.++(
db.typeAliases.get(supr.itid.get).toList.flatMap(alias => dealias(supr, alias)).map((typ, _, dealiasCost))
)
.++(db.types.get(typ.itid.get).toList.flatMap(node => specializeParents(typ, node)).map {
case t: Type if t.isGeneric != typ.isGeneric || avoid.contains(t.name) =>
(t, supr, losingInformationCost)
case t => (t, supr, subTypeCost)
})
.map {
case (t, s, cost) => cost + typeMatchQualityMetric(t, s)
}
.minOption
.getOrElse(aLotCost)
}
}

}
Loading

0 comments on commit 05475e6

Please sign in to comment.