All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.joern.kotlin2cpg.ast.AstForDeclarationsCreator.scala Maven / Gradle / Ivy

The newest version!
package io.joern.kotlin2cpg.ast

import io.joern.kotlin2cpg.Constants
import io.joern.kotlin2cpg.psi.PsiUtils
import io.joern.kotlin2cpg.psi.PsiUtils.nonUnderscoreDestructuringEntries
import io.joern.kotlin2cpg.types.AnonymousObjectContext
import io.joern.kotlin2cpg.types.TypeConstants
import io.joern.kotlin2cpg.types.TypeInfoProvider
import io.joern.x2cpg.Ast
import io.joern.x2cpg.datastructures.Stack.*
import io.joern.x2cpg.Defines
import io.joern.x2cpg.ValidationMode
import io.joern.x2cpg.utils.NodeBuilders
import io.joern.x2cpg.utils.NodeBuilders.newBindingNode
import io.joern.x2cpg.utils.NodeBuilders.newIdentifierNode
import io.joern.x2cpg.utils.NodeBuilders.newMethodReturnNode
import io.joern.x2cpg.utils.NodeBuilders.newModifierNode
import io.shiftleft.codepropertygraph.generated.DispatchTypes
import io.shiftleft.codepropertygraph.generated.EdgeTypes
import io.shiftleft.codepropertygraph.generated.Operators
import io.shiftleft.codepropertygraph.generated.nodes.NewBlock
import io.shiftleft.codepropertygraph.generated.nodes.NewCall
import io.shiftleft.codepropertygraph.generated.nodes.NewMethod
import io.shiftleft.codepropertygraph.generated.nodes.NewTypeDecl
import io.shiftleft.codepropertygraph.generated.ModifierTypes
import io.shiftleft.semanticcpg.language.*
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.psi.*

import scala.jdk.CollectionConverters.*
import scala.util.Random

trait AstForDeclarationsCreator(implicit withSchemaValidation: ValidationMode) {
  this: AstCreator =>

  private def isAbstract(ktClass: KtClassOrObject)(implicit typeInfoProvider: TypeInfoProvider): Boolean = {
    typeInfoProvider.modality(ktClass).contains(Modality.ABSTRACT)
  }

  def astsForClassOrObject(
    ktClass: KtClassOrObject,
    ctx: Option[AnonymousObjectContext] = None,
    annotations: Seq[KtAnnotationEntry] = Seq()
  )(implicit typeInfoProvider: TypeInfoProvider): Seq[Ast] = {
    val className = ctx match {
      case Some(_) => "anonymous_obj"
      case None    => ktClass.getName
    }

    val explicitFullName = {
      val fqName = ktClass.getContainingKtFile.getPackageFqName.toString
      s"$fqName.$className"
    }
    val classFullName = registerType(typeInfoProvider.fullName(ktClass, explicitFullName, ctx))
    val explicitBaseTypeFullNames = ktClass.getSuperTypeListEntries.asScala
      .map(_.getTypeAsUserType)
      .collect { case t if t != null => t.getText }
      .map { typ => typeInfoProvider.typeFromImports(typ, ktClass.getContainingKtFile).getOrElse(typ) }
      .toList

    val baseTypeFullNames = typeInfoProvider.inheritanceTypes(ktClass, explicitBaseTypeFullNames)
    baseTypeFullNames.foreach(registerType)
    val outBaseTypeFullNames = Option(baseTypeFullNames).filter(_.nonEmpty).getOrElse(Seq(TypeConstants.javaLangObject))
    val typeDecl = typeDeclNode(ktClass, className, classFullName, relativizedPath, outBaseTypeFullNames, None)
    scope.pushNewScope(typeDecl)
    methodAstParentStack.push(typeDecl)

    val primaryCtor       = ktClass.getPrimaryConstructor
    val constructorParams = ktClass.getPrimaryConstructorParameters.asScala.toList
    val defaultSignature = Option(primaryCtor)
      .map { _ => typeInfoProvider.anySignature(constructorParams) }
      .getOrElse(s"${TypeConstants.void}()")
    val defaultFullName       = s"$classFullName.${TypeConstants.initPrefix}:$defaultSignature"
    val (fullName, signature) = typeInfoProvider.fullNameWithSignature(primaryCtor, (defaultFullName, defaultSignature))
    val primaryCtorMethodNode = methodNode(primaryCtor, TypeConstants.initPrefix, fullName, signature, relativizedPath)
    val ctorThisParam =
      NodeBuilders.newThisParameterNode(typeFullName = classFullName, dynamicTypeHintFullName = Seq(classFullName))
    scope.addToScope(Constants.this_, ctorThisParam)

    val constructorParamsAsts = Seq(Ast(ctorThisParam)) ++ withIndex(constructorParams) { (p, idx) =>
      astForParameter(p, idx)
    }

    val memberSetCalls = constructorParams.collect {
      case ctorParam if ctorParam.hasValOrVar =>
        memberSetCallAst(ctorParam, classFullName)
    }

    val classDeclarations = Option(ktClass.getBody)
      .map(_.getDeclarations.asScala.filterNot(_.isInstanceOf[KtNamedFunction]))
      .getOrElse(List())

    val memberInitializerSetCalls =
      classDeclarations.collectAll[KtProperty].filter(_.getInitializer != null).map { decl =>
        val initializerAsts = astsForExpression(decl.getInitializer, None)
        val rhsAst =
          if (initializerAsts.size == 1) initializerAsts.head
          else Ast(unknownNode(decl, ""))

        val thisIdentifier = newIdentifierNode(Constants.this_, classFullName, Seq(classFullName))
        val thisAst        = astWithRefEdgeMaybe(Constants.this_, thisIdentifier)

        val fieldIdentifier = fieldIdentifierNode(decl, decl.getName, decl.getName)
        val fieldAccessCall = NodeBuilders
          .newOperatorCallNode(Operators.fieldAccess, s"${Constants.this_}.${fieldIdentifier.canonicalName}", None)
        val fieldAccessCallAst = callAst(fieldAccessCall, List(thisAst, Ast(fieldIdentifier)))

        val assignmentNode = NodeBuilders
          .newOperatorCallNode(Operators.assignment, s"${fieldAccessCall.code} = ${decl.getInitializer.getText}")
        callAst(assignmentNode, List(fieldAccessCallAst, rhsAst))
      }

    val anonymousInitExpressions = ktClass.getAnonymousInitializers.asScala
    val anonymousInitAsts        = anonymousInitExpressions.flatMap(astsForExpression(_, None))

    val constructorMethodReturn = newMethodReturnNode(
      TypeConstants.void,
      None,
      line(ktClass.getPrimaryConstructor),
      column(ktClass.getPrimaryConstructor)
    )
    val constructorAst = methodAst(
      primaryCtorMethodNode,
      constructorParamsAsts,
      blockAst(
        blockNode(ktClass, "", TypeConstants.void),
        memberSetCalls ++ memberInitializerSetCalls ++ anonymousInitAsts
      ),
      constructorMethodReturn,
      Seq(newModifierNode(ModifierTypes.CONSTRUCTOR))
    )
    val node =
      newBindingNode(primaryCtorMethodNode.name, primaryCtorMethodNode.signature, primaryCtorMethodNode.fullName)
    val ctorBindingInfo =
      BindingInfo(node, List((typeDecl, node, EdgeTypes.BINDS), (node, primaryCtorMethodNode, EdgeTypes.REF)))

    val membersFromPrimaryCtorAsts = ktClass.getPrimaryConstructorParameters.asScala.toList.collect {
      case param if param.hasValOrVar =>
        val typeFullName = registerType(typeInfoProvider.parameterType(param, TypeConstants.any))
        val memberNode_  = memberNode(param, param.getName, param.getName, typeFullName)
        scope.addToScope(param.getName, memberNode_)
        Ast(memberNode_)
    }

    val primaryCtorCall =
      callNode(
        ktClass.getPrimaryConstructor,
        TypeConstants.initPrefix,
        primaryCtorMethodNode.name,
        primaryCtorMethodNode.fullName,
        DispatchTypes.STATIC_DISPATCH,
        Some(primaryCtorMethodNode.signature),
        Some(constructorMethodReturn.typeFullName)
      )

    val secondaryConstructorAsts =
      secondaryCtorAsts(ktClass.getSecondaryConstructors.asScala.toSeq, classFullName, primaryCtorCall)
    val _componentNMethodAsts = ktClass match {
      case typedExpr: KtClass if typedExpr.isData =>
        componentNMethodAsts(typeDecl, ktClass.getPrimaryConstructor.getValueParameters.asScala.toSeq)
      case _ => Seq()
    }
    val componentNBindingsInfo = _componentNMethodAsts.flatMap(_.root.collectAll[NewMethod]).map { methodNode =>
      val node = newBindingNode(methodNode.name, methodNode.signature, methodNode.fullName)
      BindingInfo(node, List((typeDecl, node, EdgeTypes.BINDS), (node, methodNode, EdgeTypes.REF)))
    }

    val memberAsts = classDeclarations.toSeq.map(astForMember)
    val innerTypeDeclAsts =
      classDeclarations.toSeq
        .collectAll[KtClassOrObject]
        .filterNot(typeInfoProvider.isCompanionObject)
        .flatMap(astsForDeclaration(_))

    val classFunctions = Option(ktClass.getBody)
      .map(_.getFunctions.asScala.collect { case f: KtNamedFunction => f })
      .getOrElse(List())
    val methodAsts = classFunctions.toSeq.flatMap { classFn =>
      astsForMethod(classFn, needsThisParameter = true, withVirtualModifier = true)
    }
    val bindingsInfo = methodAsts.flatMap(_.root.collectAll[NewMethod]).map { _methodNode =>
      val node = newBindingNode(_methodNode.name, _methodNode.signature, _methodNode.fullName)
      BindingInfo(node, List((typeDecl, node, EdgeTypes.BINDS), (node, _methodNode, EdgeTypes.REF)))
    }

    val annotationAsts = ktClass.getAnnotationEntries.asScala.map(astForAnnotationEntry).toSeq

    val modifiers = if (isAbstract(ktClass)) List(Ast(NodeBuilders.newModifierNode(ModifierTypes.ABSTRACT))) else Nil

    val children = methodAsts ++ List(constructorAst) ++ membersFromPrimaryCtorAsts ++ secondaryConstructorAsts ++
      _componentNMethodAsts.toList ++ memberAsts ++ annotationAsts ++ modifiers
    val ast = Ast(typeDecl).withChildren(children)

    (List(ctorBindingInfo) ++ bindingsInfo ++ componentNBindingsInfo).foreach(bindingInfoQueue.prepend)

    val finalAst = if (typeInfoProvider.isCompanionObject(ktClass)) {
      val companionMemberTypeFullName = ktClass.getParent.getParent match {
        case c: KtClassOrObject => typeInfoProvider.typeFullName(c, TypeConstants.any)
        case _                  => TypeConstants.any
      }
      registerType(companionMemberTypeFullName)

      val companionObjectMember = memberNode(
        ktClass,
        Constants.companionObjectMemberName,
        Constants.companionObjectMemberName,
        companionMemberTypeFullName
      )
      ast.withChild(Ast(companionObjectMember))
    } else {
      ast
    }
    val companionObjectAsts = ktClass.getCompanionObjects.asScala.flatMap(astsForClassOrObject(_, None))
    methodAstParentStack.pop()
    scope.popScope()

    Seq(finalAst.withChildren(annotations.map(astForAnnotationEntry))) ++ companionObjectAsts ++ innerTypeDeclAsts
  }

  private def memberSetCallAst(param: KtParameter, classFullName: String)(implicit
    typeInfoProvider: TypeInfoProvider
  ): Ast = {
    val typeFullName       = registerType(typeInfoProvider.typeFullName(param, TypeConstants.any))
    val paramName          = param.getName
    val paramIdentifier    = identifierNode(param, paramName, paramName, typeFullName)
    val paramIdentifierAst = astWithRefEdgeMaybe(paramName, paramIdentifier)
    val thisIdentifier     = newIdentifierNode(Constants.this_, classFullName, Seq(classFullName))
    val thisAst            = astWithRefEdgeMaybe(Constants.this_, thisIdentifier)

    val fieldIdentifier = fieldIdentifierNode(param, paramName, paramName)
    val fieldAccessCall =
      NodeBuilders.newOperatorCallNode(Operators.fieldAccess, s"${Constants.this_}.$paramName", Option(typeFullName))
    val fieldAccessCallAst = callAst(fieldAccessCall, List(thisAst, Ast(fieldIdentifier)))

    val assignmentNode =
      NodeBuilders.newOperatorCallNode(Operators.assignment, s"${fieldAccessCall.code} = ${paramIdentifier.code}")
    callAst(assignmentNode, List(fieldAccessCallAst, paramIdentifierAst))
  }

  private def astsForDestructuringDeclarationWithRHS(
    expr: KtDestructuringDeclaration
  )(implicit typeInfoProvider: TypeInfoProvider): Seq[Ast] = {
    val typedInit = Option(expr.getInitializer).collect {
      case c: KtCallExpression           => c
      case dqe: KtDotQualifiedExpression => dqe
      case ac: KtArrayAccessExpression   => ac
      case pf: KtPostfixExpression       => pf
      case w: KtWhenExpression           => w
      case i: KtIfExpression             => i
    }
    if (typedInit.isEmpty) {
      logger.warn(
        s"Unhandled case for destructuring declaration: `${expr.getText}`; type: `${expr.getInitializer.getClass}` in this file `$relativizedPath`."
      )
      return Seq()
    }
    val rhsCall             = typedInit.get
    val callRhsTypeFullName = registerType(typeInfoProvider.expressionType(rhsCall, TypeConstants.any))

    val destructuringEntries = nonUnderscoreDestructuringEntries(expr)
    val localsForEntries = destructuringEntries.map { entry =>
      val typeFullName = registerType(typeInfoProvider.typeFullName(entry, TypeConstants.any))
      val node         = localNode(entry, entry.getName, entry.getName, typeFullName)
      scope.addToScope(node.name, node)
      Ast(node)
    }

    val isCtor = expr.getInitializer match {
      case _: KtCallExpression => typeInfoProvider.isConstructorCall(rhsCall).getOrElse(false)
      case _                   => false
    }
    val tmpName         = s"${Constants.tmpLocalPrefix}${tmpKeyPool.next}"
    val localForTmpNode = localNode(expr, tmpName, tmpName, callRhsTypeFullName)
    scope.addToScope(localForTmpNode.name, localForTmpNode)
    val localForTmpAst = Ast(localForTmpNode)

    val assignmentLhsNode = identifierNode(expr, tmpName, tmpName, localForTmpNode.typeFullName)
    val assignmentLhsAst  = Ast(assignmentLhsNode).withRefEdge(assignmentLhsNode, localForTmpNode)
    val tmpAssignmentAst =
      if (isCtor) {
        val assignmentRhsNode = NodeBuilders.newOperatorCallNode(
          Operators.alloc,
          Constants.alloc,
          Option(localForTmpNode.typeFullName),
          line(expr),
          column(expr)
        )
        val assignmentNode =
          NodeBuilders.newOperatorCallNode(Operators.assignment, s"$tmpName  = ${Constants.alloc}", None)
        callAst(assignmentNode, List(assignmentLhsAst, Ast(assignmentRhsNode)))
      } else {
        expr.getInitializer match {
          case accessExpression: KtArrayAccessExpression =>
            astForArrayAccess(accessExpression, None, None)
          case expression: KtPostfixExpression =>
            astForPostfixExpression(expression, None, None)
          case expression: KtWhenExpression =>
            astForWhenAsExpression(expression, None, None)
          case expression: KtIfExpression =>
            astForIfAsExpression(expression, None, None)
          case _ =>
            val assignmentNode =
              NodeBuilders.newOperatorCallNode(Operators.assignment, s"$tmpName = ${rhsCall.getText}", None)
            val assignmentRhsAst =
              astsForExpression(rhsCall, None).headOption.getOrElse(Ast(unknownNode(rhsCall, Constants.empty)))
            callAst(assignmentNode, List(assignmentLhsAst, assignmentRhsAst))
        }
      }
    val tmpAssignmentPrologue = rhsCall match {
      case call: KtCallExpression if isCtor =>
        val initReceiverNode =
          identifierNode(expr, tmpName, tmpName, localForTmpNode.typeFullName).argumentIndex(0)
        val initReceiverAst = Ast(initReceiverNode).withRefEdge(initReceiverNode, localForTmpNode)

        val argAsts = withIndex(call.getValueArguments.asScala.toSeq) { case (arg, idx) =>
          astsForExpression(arg.getArgumentExpression, Some(idx))
        }.flatten

        val (fullName, signature) = typeInfoProvider.fullNameWithSignature(call, (TypeConstants.any, TypeConstants.any))
        registerType(typeInfoProvider.expressionType(expr, TypeConstants.any))
        val initCallNode = callNode(
          expr,
          Constants.init,
          Constants.init,
          fullName,
          DispatchTypes.STATIC_DISPATCH,
          Some(signature),
          Some(TypeConstants.void)
        )
        Seq(callAst(initCallNode, argAsts, Some(initReceiverAst), None))
      case _ => Seq()
    }

    val assignmentsForEntries = destructuringEntries.zipWithIndex.map { case (entry, idx) =>
      assignmentAstForDestructuringEntry(entry, localForTmpNode.name, localForTmpNode.typeFullName, idx + 1)
    }
    localsForEntries ++ Seq(localForTmpAst) ++
      Seq(tmpAssignmentAst) ++ tmpAssignmentPrologue ++ assignmentsForEntries

  }

  /*
   _______ example lowering _________
  | val (one, two) = person
  |
  | -> LOCAL one
  | -> LOCAL two
  | -> CALL one = person.component1()
  | -> CALL two = person.component2()
  |__________________________________
   */
  private def astsForDestructuringDeclarationWithVarRHS(
    expr: KtDestructuringDeclaration
  )(implicit typeInfoProvider: TypeInfoProvider): Seq[Ast] = {
    val typedInit = Option(expr.getInitializer).collect { case e: KtNameReferenceExpression => e }
    if (typedInit.isEmpty) {
      logger.warn(s"Unhandled case for destructuring declaration: `${expr.getText}` in this file `$relativizedPath`.")
      return Seq()
    }
    val destructuringRHS = typedInit.get

    val initTypeFullName = registerType(typeInfoProvider.typeFullName(typedInit.get, TypeConstants.any))
    val assignmentsForEntries =
      nonUnderscoreDestructuringEntries(expr).zipWithIndex.map { case (entry, idx) =>
        assignmentAstForDestructuringEntry(entry, destructuringRHS.getText, initTypeFullName, idx + 1)
      }
    val localsForEntries = nonUnderscoreDestructuringEntries(expr).map { entry =>
      val typeFullName = registerType(typeInfoProvider.typeFullName(entry, TypeConstants.any))
      val node         = localNode(entry, entry.getName, entry.getName, typeFullName)
      scope.addToScope(node.name, node)
      Ast(node)
    }
    localsForEntries ++ assignmentsForEntries
  }

  def astsForDestructuringDeclaration(
    expr: KtDestructuringDeclaration
  )(implicit typeInfoProvider: TypeInfoProvider): Seq[Ast] = {
    val hasNonRefExprRHS = expr.getInitializer match {
      case _: KtNameReferenceExpression => false
      case _: KtExpression              => true
      case null                         => false
    }
    if (hasNonRefExprRHS) astsForDestructuringDeclarationWithRHS(expr)
    else astsForDestructuringDeclarationWithVarRHS(expr)
  }

  private def componentNMethodAsts(typeDecl: NewTypeDecl, parameters: Seq[KtParameter])(implicit
    typeInfoProvider: TypeInfoProvider
  ): Seq[Ast] = {
    parameters.zipWithIndex.map { case (valueParam, idx) =>
      val typeFullName = registerType(typeInfoProvider.typeFullName(valueParam, TypeConstants.any))

      val thisParam =
        NodeBuilders.newThisParameterNode(typeFullName = typeDecl.fullName, dynamicTypeHintFullName = Seq())
      val thisIdentifier = newIdentifierNode(Constants.this_, typeDecl.fullName, Seq(typeDecl.fullName))
      val thisAst        = Ast(thisIdentifier).withRefEdge(thisIdentifier, thisParam)

      val fieldIdentifier = fieldIdentifierNode(valueParam, valueParam.getName, valueParam.getName)
      val fieldAccessCall = NodeBuilders.newOperatorCallNode(
        Operators.fieldAccess,
        s"${Constants.this_}.${valueParam.getName}",
        Option(typeFullName)
      )
      val fieldAccessCallAst = callAst(fieldAccessCall, List(thisAst, Ast(fieldIdentifier)))
      val methodBlockAst = blockAst(
        blockNode(valueParam, fieldAccessCall.code, typeFullName),
        List(returnAst(returnNode(valueParam, Constants.ret), List(fieldAccessCallAst)))
      )

      val componentIdx  = idx + 1
      val componentName = s"${Constants.componentNPrefix}$componentIdx"
      val signature     = s"$typeFullName()"
      val fullName      = s"${typeDecl.fullName}.$componentName:$signature"
      methodAst(
        methodNode(valueParam, componentName, fullName, signature, relativizedPath),
        Seq(Ast(thisParam)),
        methodBlockAst,
        newMethodReturnNode(typeFullName, None, None, None)
      )
    }
  }

  private def secondaryCtorAsts(ctors: Seq[KtSecondaryConstructor], classFullName: String, primaryCtorCall: NewCall)(
    implicit typeInfoProvider: TypeInfoProvider
  ): Seq[Ast] = {
    ctors.map { ctor =>
      val primaryCtorCallAst    = List(Ast(primaryCtorCall.copy))
      val constructorParams     = ctor.getValueParameters.asScala.toList
      val defaultSignature      = typeInfoProvider.anySignature(constructorParams)
      val defaultFullName       = s"$classFullName.${TypeConstants.initPrefix}:$defaultSignature"
      val (fullName, signature) = typeInfoProvider.fullNameWithSignature(ctor, (defaultFullName, defaultSignature))
      val secondaryCtorMethodNode =
        methodNode(ctor, Constants.init, fullName, signature, relativizedPath)
      scope.pushNewScope(secondaryCtorMethodNode)

      val ctorThisParam =
        NodeBuilders.newThisParameterNode(typeFullName = classFullName, dynamicTypeHintFullName = Seq(classFullName))
      scope.addToScope(Constants.this_, ctorThisParam)

      val constructorParamsAsts = Seq(Ast(ctorThisParam)) ++
        withIndex(constructorParams) { (p, idx) => astForParameter(p, idx) }

      val ctorMethodBlockAsts =
        ctor.getBodyExpression match {
          case b: KtBlockExpression =>
            astsForBlock(b, None, None, preStatements = Option(primaryCtorCallAst))
          case null =>
            val node = NewBlock().code(Constants.empty).typeFullName(TypeConstants.any)
            Seq(blockAst(node, primaryCtorCallAst))
        }
      scope.popScope()

      val ctorMethodReturnNode =
        newMethodReturnNode(TypeConstants.void, None, line(ctor), column(ctor))

      // TODO: see if necessary to take the other asts for the ctorMethodBlock
      methodAst(
        secondaryCtorMethodNode,
        constructorParamsAsts,
        ctorMethodBlockAsts.headOption.getOrElse(Ast(unknownNode(ctor.getBodyExpression, Constants.empty))),
        ctorMethodReturnNode,
        Seq(newModifierNode(ModifierTypes.CONSTRUCTOR))
      )
    }
  }

  protected def astForObjectLiteralExpr(
    expr: KtObjectLiteralExpression,
    argIdxMaybe: Option[Int],
    argNameMaybe: Option[String],
    annotations: Seq[KtAnnotationEntry] = Seq()
  )(implicit typeInfoProvider: TypeInfoProvider): Ast = {
    val parentFn = KtPsiUtil.getTopmostParentOfTypes(expr, classOf[KtNamedFunction])
    val ctx =
      Option(parentFn)
        .collect { case namedFn: KtNamedFunction => namedFn }
        .map(AnonymousObjectContext(_))
        .getOrElse(AnonymousObjectContext(expr.getContainingKtFile))

    val idxOpt  = PsiUtils.objectIdxMaybe(expr.getObjectDeclaration, parentFn)
    val idx     = idxOpt.getOrElse(Random.nextInt())
    val tmpName = s"tmp_obj_$idx"

    val typeDeclAsts = astsForClassOrObject(expr.getObjectDeclaration, Some(ctx))
    val typeDeclAst  = typeDeclAsts.headOption.getOrElse(Ast(unknownNode(expr.getObjectDeclaration, Constants.empty)))
    val typeDeclFullName = typeDeclAst.root.get.asInstanceOf[NewTypeDecl].fullName

    val localForTmp = localNode(expr, tmpName, tmpName, typeDeclFullName)
    scope.addToScope(tmpName, localForTmp)
    val localAst = Ast(localForTmp)

    val rhsAst = Ast(NodeBuilders.newOperatorCallNode(Operators.alloc, Operators.alloc, None))

    val identifier    = identifierNode(expr, tmpName, tmpName, localForTmp.typeFullName)
    val identifierAst = astWithRefEdgeMaybe(identifier.name, identifier)

    val assignmentNode = NodeBuilders.newOperatorCallNode(
      Operators.assignment,
      s"${identifier.name} = ",
      None,
      line(expr),
      column(expr)
    )
    val assignmentCallAst = callAst(assignmentNode, List(identifierAst) ++ List(rhsAst))
    val initSignature     = s"${TypeConstants.void}()"
    val initFullName      = s"$typeDeclFullName.${TypeConstants.initPrefix}:$initSignature"
    val initCallNode =
      callNode(
        expr,
        Constants.init,
        Constants.init,
        initFullName,
        DispatchTypes.STATIC_DISPATCH,
        Some(initSignature),
        Some(TypeConstants.void)
      )

    val initReceiverNode = identifierNode(expr, identifier.name, identifier.name, identifier.typeFullName)
    val initReceiverAst =
      Ast(initReceiverNode).withRefEdge(initReceiverNode, localForTmp)
    val initAst = callAst(initCallNode, Seq(), Option(initReceiverAst))

    val refTmpNode = identifierNode(expr, tmpName, tmpName, localForTmp.typeFullName)
    val refTmpAst  = astWithRefEdgeMaybe(refTmpNode.name, refTmpNode)

    val blockNode_ =
      withArgumentIndex(blockNode(expr, expr.getText, TypeConstants.any), argIdxMaybe)
        .argumentName(argNameMaybe)
    blockAst(blockNode_, Seq(typeDeclAst, localAst, assignmentCallAst, initAst, refTmpAst).toList)
      .withChildren(annotations.map(astForAnnotationEntry))
  }

  def astsForProperty(expr: KtProperty, annotations: Seq[KtAnnotationEntry] = Seq())(implicit
    typeInfoProvider: TypeInfoProvider
  ): Seq[Ast] = {
    val explicitTypeName = Option(expr.getTypeReference).map(_.getText).getOrElse(TypeConstants.any)
    val elem             = expr.getIdentifyingElement

    val hasRHSCtorCall =
      Option(expr.getDelegateExpressionOrInitializer).flatMap(typeInfoProvider.isConstructorCall).getOrElse(false)
    val ctorCallExprMaybe =
      if (hasRHSCtorCall) {
        expr.getDelegateExpressionOrInitializer match {
          case c: KtCallExpression => Some(c)
          case q: KtQualifiedExpression =>
            q.getSelectorExpression match {
              case qc: KtCallExpression => Some(qc)
              case _                    => None
            }
          case _ => None
        }
      } else None
    val hasRHSObjectLiteral = expr.getDelegateExpressionOrInitializer match {
      case _: KtObjectLiteralExpression => true
      case _                            => false
    }
    if (ctorCallExprMaybe.nonEmpty) {
      val callExpr          = ctorCallExprMaybe.get
      val localTypeFullName = registerType(typeInfoProvider.propertyType(expr, explicitTypeName))
      val local             = localNode(expr, expr.getName, expr.getName, localTypeFullName)
      scope.addToScope(expr.getName, local)
      val localAst = Ast(local)

      val typeFullName = registerType(
        typeInfoProvider.expressionType(expr.getDelegateExpressionOrInitializer, Defines.UnresolvedNamespace)
      )
      val rhsAst = Ast(NodeBuilders.newOperatorCallNode(Operators.alloc, Operators.alloc, Option(typeFullName)))

      val identifier    = identifierNode(elem, elem.getText, elem.getText, local.typeFullName)
      val identifierAst = astWithRefEdgeMaybe(identifier.name, identifier)

      val assignmentNode =
        NodeBuilders.newOperatorCallNode(Operators.assignment, expr.getText, None, line(expr), column(expr))
      val assignmentCallAst = callAst(assignmentNode, List(identifierAst) ++ List(rhsAst))

      val (fullName, signature) =
        typeInfoProvider.fullNameWithSignature(callExpr, (TypeConstants.any, TypeConstants.any))
      val initCallNode = callNode(
        callExpr,
        callExpr.getText,
        Constants.init,
        fullName,
        DispatchTypes.STATIC_DISPATCH,
        Some(signature),
        Some(TypeConstants.void)
      )
      val initReceiverNode = identifierNode(expr, identifier.name, identifier.name, identifier.typeFullName)
      val initReceiverAst  = Ast(initReceiverNode).withRefEdge(initReceiverNode, local)

      val argAsts = withIndex(callExpr.getValueArguments.asScala.toSeq) { case (arg, idx) =>
        val argNameOpt = if (arg.isNamed) Option(arg.getArgumentName.getAsName.toString) else None
        astsForExpression(arg.getArgumentExpression, Option(idx), argNameOpt)
      }.flatten

      val initAst =
        callAst(initCallNode, argAsts, Option(initReceiverAst))
          .withChildren(annotations.map(astForAnnotationEntry))
      Seq(localAst, assignmentCallAst, initAst)
    } else if (hasRHSObjectLiteral) {
      val typedExpr = expr.getDelegateExpressionOrInitializer.asInstanceOf[KtObjectLiteralExpression]
      val parentFn  = KtPsiUtil.getTopmostParentOfTypes(expr, classOf[KtNamedFunction])
      val ctx =
        Option(parentFn)
          .collect { case namedFn: KtNamedFunction => namedFn }
          .map(AnonymousObjectContext(_))
          .getOrElse(AnonymousObjectContext(expr.getContainingKtFile))

      val typeDeclAsts = astsForClassOrObject(typedExpr.getObjectDeclaration, Some(ctx))
      val typeDeclAst =
        typeDeclAsts.headOption.getOrElse(Ast(unknownNode(typedExpr.getObjectDeclaration, Constants.empty)))
      val typeDeclFullName = typeDeclAst.root.get.asInstanceOf[NewTypeDecl].fullName

      val node = localNode(expr, expr.getName, expr.getName, typeDeclFullName)
      scope.addToScope(expr.getName, node)
      val localAst = Ast(node)

      val typeFullName = registerType(
        typeInfoProvider.expressionType(expr.getDelegateExpressionOrInitializer, Defines.UnresolvedNamespace)
      )
      val rhsAst = Ast(NodeBuilders.newOperatorCallNode(Operators.alloc, Operators.alloc, None))

      val identifier    = identifierNode(elem, elem.getText, elem.getText, node.typeFullName)
      val identifierAst = astWithRefEdgeMaybe(identifier.name, identifier)

      val assignmentNode =
        NodeBuilders.newOperatorCallNode(Operators.assignment, expr.getText, None, line(expr), column(expr))
      val assignmentCallAst = callAst(assignmentNode, List(identifierAst) ++ List(rhsAst))
      val initSignature     = s"${TypeConstants.void}()"
      val initFullName      = s"$typeFullName${TypeConstants.initPrefix}:$initSignature"
      val initCallNode = callNode(
        expr,
        Constants.init,
        Constants.init,
        initFullName,
        DispatchTypes.STATIC_DISPATCH,
        Some(initSignature),
        Some(TypeConstants.void)
      )
      val initReceiverNode = identifierNode(expr, identifier.name, identifier.name, identifier.typeFullName)
      val initReceiverAst  = Ast(initReceiverNode).withRefEdge(initReceiverNode, node)

      val initAst =
        callAst(initCallNode, Seq(), Option(initReceiverAst))
          .withChildren(annotations.map(astForAnnotationEntry))
      Seq(typeDeclAst, localAst, assignmentCallAst, initAst)
    } else {
      val typeFullName = registerType(typeInfoProvider.propertyType(expr, explicitTypeName))
      val node         = localNode(expr, expr.getName, expr.getName, typeFullName)
      scope.addToScope(expr.getName, node)
      val localAst = Ast(node)

      val rhsAsts       = astsForExpression(expr.getDelegateExpressionOrInitializer, Some(2))
      val identifier    = identifierNode(elem, elem.getText, elem.getText, typeFullName)
      val identifierAst = astWithRefEdgeMaybe(identifier.name, identifier)
      val assignmentNode =
        NodeBuilders.newOperatorCallNode(Operators.assignment, expr.getText, None, line(expr), column(expr))
      val call =
        callAst(assignmentNode, List(identifierAst) ++ rhsAsts)
          .withChildren(annotations.map(astForAnnotationEntry))
      Seq(localAst, call)
    }
  }

  private def astForMember(decl: KtDeclaration)(implicit typeInfoProvider: TypeInfoProvider): Ast = {
    val name = Option(decl.getName).getOrElse(TypeConstants.any)
    val explicitTypeName = decl.getOriginalElement match {
      case p: KtProperty if p.getTypeReference != null => p.getTypeReference.getText
      case _                                           => TypeConstants.any
    }
    val typeFullName = decl match {
      case typed: KtProperty => typeInfoProvider.propertyType(typed, explicitTypeName)
      case _                 => explicitTypeName
    }
    registerType(typeFullName)

    val node = memberNode(decl, name, name, typeFullName)
    scope.addToScope(name, node)
    Ast(node)
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy