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

Sorting #210

Merged
merged 5 commits into from
Aug 26, 2021
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
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