<commit_before>package com.avsystem.scex
package validation

import SymbolValidator._
import com.avsystem.scex.util.CommonUtils._
import com.avsystem.scex.util.LoggingUtils
import java.{util => ju, lang => jl}
import scala.collection.immutable.{SortedSet, TreeSet}
import scala.language.dynamics
import scala.language.experimental.macros
import scala.language.implicitConversions
import scala.annotation.{StaticAnnotation, compileTimeOnly}

trait SymbolValidator extends LoggingUtils {
  private val logger = createLogger[SymbolValidator]

  val accessSpecs: List[MemberAccessSpec]

  def combine(otherValidator: SymbolValidator) =
    SymbolValidator(accessSpecs ++ otherValidator.accessSpecs)

  lazy val referencedJavaClasses = accessSpecs.collect({
    case MemberAccessSpec(typeInfo, _, _, true) if typeInfo.clazz.isDefined && typeInfo.isJava =>
      hierarchy(typeInfo.clazz.get)
  }).flatten.toSet

  private lazy val specsLength = accessSpecs.length

  private def lowestPriority(allowedByDefault: Boolean) =
    if (allowedByDefault) specsLength else specsLength + 1

  // MemberAccessSpec with its index in ACL (accessSpecs)
  private type SpecWithIndex = (MemberAccessSpec, Int)

  private lazy val bySignaturesMap: Map[String, List[SpecWithIndex]] =
    accessSpecs.zipWithIndex.groupBy(_._1.memberSignature).withDefaultValue(Nil)

  private lazy val memberSignatures: SortedSet[String] =
    bySignaturesMap.keys.to[TreeSet]

  def referencesModuleMember(moduleSymbolFullName: String) = {
    val prefix = moduleSymbolFullName + "."
    val projection = memberSignatures.from(prefix)
    projection.nonEmpty && projection.firstKey.startsWith(prefix)
  }

  def validateMemberAccess(vc: ValidationContext)(access: vc.MemberAccess): vc.ValidationResult = {
    import vc._
    import vc.universe._

    def implicitConversionsMatch(actual: Option[Tree], fromSpec: Option[String]) =
      (actual, fromSpec) match {
        case (Some(actualPathTree), Some(specPath)) =>
          path(actualPathTree) == specPath
        case (None, None) => true
        case _ => false
      }

    access match {
      case access@SimpleMemberAccess(tpe, symbol, implicitConv, allowedByDefault, position) =>
        logger.trace(s"Validating access: $access")

        val signatures: List[String] =
          (symbol :: symbol.overrides).map(memberSignature)

        // MemberAccessSpecs that match this invocation
        val matchingSpecs: List[SpecWithIndex] = signatures.flatMap { signature =>
          bySignaturesMap(signature).filter { case (accessSpec, _) =>
            signature == accessSpec.memberSignature &&
              tpe <:< accessSpec.typeInfo.typeIn(vc.universe) &&
              implicitConversionsMatch(implicitConv, accessSpec.implicitConv)
          }
        }

        def specsRepr = matchingSpecs.map({ case (spec, idx) => s"$idx: $spec"}).mkString("\n")
        logger.trace(s"Matching signatures:\n$specsRepr")

        // get 'allow' field from matching spec that appeared first in ACL or false if there was no matching spec
        val (allow, index) = if (matchingSpecs.nonEmpty) {
          val (spec, index) = matchingSpecs.minBy(_._2)
          (spec.allow, index)
        } else (allowedByDefault, lowestPriority(allowedByDefault))

        ValidationResult(index, if (allow) Nil else List(access))

      case MultipleMemberAccesses(accesses) =>
        // all must be allowed
        val (allowed, denied) = accesses.map(a => validateMemberAccess(vc)(a)).partition(_.deniedAccesses.isEmpty)
        if (denied.nonEmpty)
          ValidationResult(denied.minBy(_.priority).priority, denied.flatMap(_.deniedAccesses))
        else if (allowed.nonEmpty)
          ValidationResult(allowed.maxBy(_.priority).priority, Nil)
        else
          ValidationResult(lowestPriority(allowedByDefault = true), Nil)

      case AlternativeMemberAccess(accesses) =>
        // take the soonest-validated alternative
        if (accesses.nonEmpty)
          accesses.map(a => validateMemberAccess(vc)(a)).minBy(_.priority)
        else
          ValidationResult(lowestPriority(allowedByDefault = true), Nil)

      case NoMemberAccess =>
        ValidationResult(lowestPriority(allowedByDefault = true), Nil)
    }
  }
}

object SymbolValidator {

  import SymbolValidatorMacros._

  /**
   * Type annotation that serves as an alternative way of expressing existential types (types with wildcards) with
   * higher bounds in the symbol validator DSL.
   *
   * For example, imagine you want to allow invoking <tt>insert</tt> method on all lists of type
   * <tt>java.util.List[_ <: Number]</tt>. Unfortunately, you can't do it this way:
   *
   * <pre>
   * allow {
   *   on { l: java.util.List[_ <: Number] =>
   *     l.insert _
   *   }
   * }
   * </pre>
   *
   *
   *
   *
   */
  class plus extends StaticAnnotation

  class minus extends StaticAnnotation<commit_msg>javadocs<commit_after>package com.avsystem.scex
package validation

import SymbolValidator._
import com.avsystem.scex.util.CommonUtils._
import com.avsystem.scex.util.LoggingUtils
import java.{util => ju, lang => jl}
import scala.collection.immutable.{SortedSet, TreeSet}
import scala.language.dynamics
import scala.language.experimental.macros
import scala.language.implicitConversions
import scala.annotation.{StaticAnnotation, compileTimeOnly}

trait SymbolValidator extends LoggingUtils {
  private val logger = createLogger[SymbolValidator]

  val accessSpecs: List[MemberAccessSpec]

  def combine(otherValidator: SymbolValidator) =
    SymbolValidator(accessSpecs ++ otherValidator.accessSpecs)

  lazy val referencedJavaClasses = accessSpecs.collect({
    case MemberAccessSpec(typeInfo, _, _, true) if typeInfo.clazz.isDefined && typeInfo.isJava =>
      hierarchy(typeInfo.clazz.get)
  }).flatten.toSet

  private lazy val specsLength = accessSpecs.length

  private def lowestPriority(allowedByDefault: Boolean) =
    if (allowedByDefault) specsLength else specsLength + 1

  // MemberAccessSpec with its index in ACL (accessSpecs)
  private type SpecWithIndex = (MemberAccessSpec, Int)

  private lazy val bySignaturesMap: Map[String, List[SpecWithIndex]] =
    accessSpecs.zipWithIndex.groupBy(_._1.memberSignature).withDefaultValue(Nil)

  private lazy val memberSignatures: SortedSet[String] =
    bySignaturesMap.keys.to[TreeSet]

  def referencesModuleMember(moduleSymbolFullName: String) = {
    val prefix = moduleSymbolFullName + "."
    val projection = memberSignatures.from(prefix)
    projection.nonEmpty && projection.firstKey.startsWith(prefix)
  }

  def validateMemberAccess(vc: ValidationContext)(access: vc.MemberAccess): vc.ValidationResult = {
    import vc._
    import vc.universe._

    def implicitConversionsMatch(actual: Option[Tree], fromSpec: Option[String]) =
      (actual, fromSpec) match {
        case (Some(actualPathTree), Some(specPath)) =>
          path(actualPathTree) == specPath
        case (None, None) => true
        case _ => false
      }

    access match {
      case access@SimpleMemberAccess(tpe, symbol, implicitConv, allowedByDefault, position) =>
        logger.trace(s"Validating access: $access")

        val signatures: List[String] =
          (symbol :: symbol.overrides).map(memberSignature)

        // MemberAccessSpecs that match this invocation
        val matchingSpecs: List[SpecWithIndex] = signatures.flatMap { signature =>
          bySignaturesMap(signature).filter { case (accessSpec, _) =>
            signature == accessSpec.memberSignature &&
              tpe <:< accessSpec.typeInfo.typeIn(vc.universe) &&
              implicitConversionsMatch(implicitConv, accessSpec.implicitConv)
          }
        }

        def specsRepr = matchingSpecs.map({ case (spec, idx) => s"$idx: $spec"}).mkString("\n")
        logger.trace(s"Matching signatures:\n$specsRepr")

        // get 'allow' field from matching spec that appeared first in ACL or false if there was no matching spec
        val (allow, index) = if (matchingSpecs.nonEmpty) {
          val (spec, index) = matchingSpecs.minBy(_._2)
          (spec.allow, index)
        } else (allowedByDefault, lowestPriority(allowedByDefault))

        ValidationResult(index, if (allow) Nil else List(access))

      case MultipleMemberAccesses(accesses) =>
        // all must be allowed
        val (allowed, denied) = accesses.map(a => validateMemberAccess(vc)(a)).partition(_.deniedAccesses.isEmpty)
        if (denied.nonEmpty)
          ValidationResult(denied.minBy(_.priority).priority, denied.flatMap(_.deniedAccesses))
        else if (allowed.nonEmpty)
          ValidationResult(allowed.maxBy(_.priority).priority, Nil)
        else
          ValidationResult(lowestPriority(allowedByDefault = true), Nil)

      case AlternativeMemberAccess(accesses) =>
        // take the soonest-validated alternative
        if (accesses.nonEmpty)
          accesses.map(a => validateMemberAccess(vc)(a)).minBy(_.priority)
        else
          ValidationResult(lowestPriority(allowedByDefault = true), Nil)

      case NoMemberAccess =>
        ValidationResult(lowestPriority(allowedByDefault = true), Nil)
    }
  }
}

object SymbolValidator {

  import SymbolValidatorMacros._

  /**
   * Type annotation that serves as an alternative way of expressing existential types (types with wildcards) with
   * higher bounds in the symbol validator DSL.
   * </p>
   * For example, imagine you want to allow invoking <tt>insert</tt> method on all lists of type
   * <tt>java.util.List[_ <: Number]</tt>. Unfortunately, the following will NOT work:
   * <pre>
   * allow {
   *   on { l: java.util.List[_ <: Number] =>
   *     l.insert _  // won't typecheck
   *   }
   * }
   * </pre>
   * </p>
   * Scala typechecker will report an error on <tt>l.insert _</tt> because this method cannot be called
   * when the element type of the list is unknown.
   * </p>
   * To overcome this limitation, you can use alternative syntax to express the same:
   * <pre>
   * allow {
   *   on { l: java.util.List[Number@plus] =>
   *     l.insert _
   *   }
   * }
   * </pre>
   */
  class plus extends StaticAnnotation

  /**
   * Analogous to {@link com.avsystem.scex.validation.SymbolValidator.plus plus},
   * but used to express wildcards with lower bounds, e.g. <tt>java.lang.List[_ >: String]</tt>
   */
  class minus extends StaticAnnotation
{'fraction_non_alphanumeric': 0.1020722891566265, 'fraction_numerical': 0.0005783132530120482, 'mean_word_length': 3.4322938914993593, 'pattern_counts': {'":': 0, '<': 32, '<?xml version=': 0, '>': 58, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1696765', 'n_tokens_mistral': 3034, 'n_tokens_neox': 2916, 'n_words': 720}