Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -288,17 +288,20 @@ object AstCreator {
}

object NameConstants {
val Default: String = "default"
val HaltCompiler: String = "__halt_compiler"
val This: String = "this"
val Self: String = "self"
val Unknown: String = "UNKNOWN"
val Closure: String = "__closure"
val Class: String = "class"
val True: String = "true"
val False: String = "false"
val NullName: String = "null"
val Invoke: String = "__invoke"
val Default: String = "default"
val HaltCompiler: String = "__halt_compiler"
val This: String = "this"
val Self: String = "self"
val Unknown: String = "UNKNOWN"
val Closure: String = "__closure"
val Class: String = "class"
val True: String = "true"
val False: String = "false"
val NullName: String = "null"
val Invoke: String = "__invoke"
val Static: String = "static"
val Parent: String = "parent"
val StaticReceiver: String = "<staticReceiver>"

def isBoolean(name: String): Boolean = {
List(True, False).contains(name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ trait AstCreatorHelper(disableFileContent: Boolean)(implicit withSchemaValidatio
protected def getTypeDeclPrefix: Option[String] =
scope.getEnclosingTypeDeclTypeName.filterNot(_ == NamespaceTraversal.globalNamespaceName)

protected def getInheritedTypeFullName: Option[String] =
scope.getEnclosingTypeDecl.flatMap(_.inheritsFromTypeFullName.headOption)

protected def codeForMethodCall(call: PhpCallExpr, targetAst: Ast, name: String): String = {
val callOperator = if (call.isNullSafe) s"?$InstanceMethodDelimiter" else InstanceMethodDelimiter
s"${targetAst.rootCodeOrEmpty}$callOperator$name"
Expand Down Expand Up @@ -277,10 +280,20 @@ trait AstCreatorHelper(disableFileContent: Boolean)(implicit withSchemaValidatio
.last
}

protected def getSimpleName(fullName: String): String = fullName.split("\\\\").last

protected def getMfn(call: PhpCallExpr, name: String): String = {
lazy val default = s"$UnresolvedNamespace$MethodDelimiter$name"
lazy val maybeResolvedFunction = scope.resolveFunctionIdentifier(name)
lazy val default = s"$UnresolvedNamespace$MethodDelimiter$name"
lazy val maybeResolvedFunction = scope.resolveFunctionIdentifier(name)
lazy val maybeInheritedTypeFullName = getInheritedTypeFullName
call.target match {
case Some(nameExpr: PhpNameExpr) if call.isStatic && nameExpr.name == NameConstants.Static =>
// static:: late static binding call handled separately as we consider it a dynamic call
composeMethodFullNameForCall(name)
case Some(nameExpr: PhpNameExpr)
if call.isStatic && nameExpr.name == NameConstants.Parent && maybeInheritedTypeFullName.isDefined =>
// Static parent:: method call
s"${maybeInheritedTypeFullName.get}$MethodDelimiter$name"
case Some(nameExpr: PhpNameExpr) if call.isStatic =>
// Static method call with a simple receiver
if (nameExpr.name == NameConstants.Self)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,21 +84,34 @@ trait AstForExpressionsCreator(implicit withSchemaValidation: ValidationMode) {
call.target match {
case None if isCallOnVariable(call) => astForDynamicCall(call, name, arguments, None)
case None => astForStaticCall(call, name, arguments)
case _ if call.isStatic => astForStaticCall(call, name, arguments)
case maybeTarget => astForDynamicCall(call, name, arguments, maybeTarget)
case maybeTarget @ Some(expr: PhpNameExpr) if call.isStatic && expr.name == NameConstants.Static =>
// Late static binding calls (static::foo()) are dynamic calls resolved at runtime.
astForDynamicCall(call, name, arguments, maybeTarget, isLateStaticBindingCall = true)
case _ if call.isStatic => astForStaticCall(call, name, arguments)
case maybeTarget => astForDynamicCall(call, name, arguments, maybeTarget)
}
}

private def astForDynamicCall(
call: PhpCallExpr,
name: String,
arguments: Seq[Ast],
maybeTarget: Option[PhpExpr]
maybeTarget: Option[PhpExpr],
isLateStaticBindingCall: Boolean = false
): Ast = {
val argsCode = getArgsCode(call, arguments)
val targetAst = maybeTarget.map(astForExpr)
val codePrefix = targetAst.map(codeForMethodCall(call, _, name)).getOrElse(name)
val code = s"$codePrefix($argsCode)"
val argsCode = getArgsCode(call, arguments)
val targetAst = if (isLateStaticBindingCall) {
maybeTarget.flatMap(astForClassScopeResolutionTarget)
} else {
maybeTarget.map(astForExpr)
}

val codePrefix = if (isLateStaticBindingCall && targetAst.isDefined) {
s"${NameConstants.Static}::$name"
} else {
targetAst.map(codeForMethodCall(call, _, name)).getOrElse(name)
}
val code = s"$codePrefix($argsCode)"

val dispatchType = DispatchTypes.DYNAMIC_DISPATCH

Expand Down Expand Up @@ -126,6 +139,19 @@ trait AstForExpressionsCreator(implicit withSchemaValidation: ValidationMode) {

}

private def astForClassScopeResolutionTarget(expr: PhpExpr): Option[Ast] = {
expr match {
case t: PhpNameExpr =>
scope.surroundingMethodReceiver.map { recv =>
val target = t.copy(name = recv)
astForNameExpr(target, code = Some(recv))
}
case expr =>
logger.warn(s"Expected a PhpNameExpr target but got $expr.")
None
}
}

private def astForStaticCall(call: PhpCallExpr, name: String, arguments: Seq[Ast]): Ast = {
val argsCode = getArgsCode(call, arguments)
val codePrefix = codeForStaticMethodCall(call, name)
Expand All @@ -139,7 +165,17 @@ trait AstForExpressionsCreator(implicit withSchemaValidation: ValidationMode) {
case _ => getMfn(call, name)
}

val targetArgument = call.target.collect {
case nameExpr: PhpNameExpr if Set(NameConstants.Parent, NameConstants.Self).contains(nameExpr.name) =>
astForClassScopeResolutionTarget(nameExpr)
case nameExpr: PhpNameExpr =>
val typ = typeRefNode(nameExpr, getSimpleName(nameExpr.name), nameExpr.name)
Some(Ast(typ))
}.flatten

val staticReceiver = call.target.collect {
case nameExpr: PhpNameExpr if nameExpr.name == NameConstants.Parent =>
getInheritedTypeFullName
case nameExpr: PhpNameExpr if nameExpr.name == NameConstants.Self =>
getTypeDeclPrefix
case nameExpr: PhpNameExpr =>
Expand All @@ -148,7 +184,9 @@ trait AstForExpressionsCreator(implicit withSchemaValidation: ValidationMode) {

val callRoot = callNode(call, code, name, fullName, dispatchType, None, Some(Defines.Any), staticReceiver)

callAst(callRoot, arguments)
val allArgs = List(targetArgument, arguments).flatten
val startArgumentIndex = Option.when(targetArgument.isDefined)(0)
staticCallAst(callRoot, allArgs, startArgumentIndex = startArgumentIndex)
}

protected def simpleAssignAst(origin: PhpNode, target: Ast, source: Ast): Ast = {
Expand Down Expand Up @@ -697,8 +735,8 @@ trait AstForExpressionsCreator(implicit withSchemaValidation: ValidationMode) {
valueAst
}

private def astForNameExpr(expr: PhpNameExpr): Ast = {
val identifier = identifierNode(expr, expr.name, expr.name, Defines.Any)
private def astForNameExpr(expr: PhpNameExpr, code: Option[String] = None): Ast = {
val identifier = identifierNode(expr, expr.name, code.getOrElse(expr.name), Defines.Any)

val declaringNode = handleVariableOccurrence(expr, identifier.name)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ trait AstForFunctionsCreator(implicit withSchemaValidation: ValidationMode) { th
val fullName = fullNameOverride.getOrElse(composeMethodFullName(methodName))

val constructorModifier = Option.when(isConstructor)(ModifierTypes.CONSTRUCTOR)
val virtualModifier = Option.unless(isStatic || isConstructor)(ModifierTypes.VIRTUAL)
val virtualModifier = Option.unless(isConstructor)(ModifierTypes.VIRTUAL)
val defaultAccessModifier = Option.unless(containsAccessModifier(decl.modifiers))(ModifierTypes.PUBLIC)

val allModifiers = virtualModifier ++: constructorModifier ++: defaultAccessModifier ++: decl.modifiers
Expand Down Expand Up @@ -158,9 +158,13 @@ trait AstForFunctionsCreator(implicit withSchemaValidation: ValidationMode) { th
MethodScope(method, methodBodyNode, method.fullName, decl.params.map(_.name), methodRef, isArrowClosure)
)

val staticReceiver = Option.when(decl.isClassMethod && isStatic) {
staticReceiverAstForMethod(decl)
}

val thisParam =
if (!isAnonymousMethod && decl.isClassMethod && !isStatic) Option(thisParamAstForMethod(decl)) else None
val parameters = thisParam.toList ++ decl.params.zipWithIndex.map { case (param, idx) =>
val parameters = thisParam.toList ++ staticReceiver.toList ++ decl.params.zipWithIndex.map { case (param, idx) =>
astForParam(param, idx + 1)
}
parameters.flatMap(_.root).foreach {
Expand Down Expand Up @@ -254,12 +258,29 @@ trait AstForFunctionsCreator(implicit withSchemaValidation: ValidationMode) { th
isVariadic = false,
evaluationStrategy = EvaluationStrategies.BY_SHARING,
typeFullName = typeFullName
).dynamicTypeHintFullName(typeFullName :: Nil)
// TODO Add dynamicTypeHintFullName to parameterInNode param list
)

Ast(thisNode)
}

private def staticReceiverAstForMethod(originNode: PhpNode): Ast = {
val typeFullName = scope.getEnclosingTypeDeclTypeFullName.getOrElse(Defines.Any)

val node = parameterInNode(
originNode,
name = NameConstants.StaticReceiver,
code = NameConstants.StaticReceiver,
index = 0,
isVariadic = false,
evaluationStrategy = EvaluationStrategies.BY_SHARING,
typeFullName = typeFullName
)

scope.addToScope(NameConstants.StaticReceiver, node)

Ast(node)
}

protected def thisIdentifier(originNode: PhpNode): NewIdentifier = {
val typ = scope.getEnclosingTypeDeclTypeName
identifierNode(originNode, NameConstants.This, s"$$${NameConstants.This}", typ.getOrElse(Defines.Any), typ.toList)
Expand Down Expand Up @@ -309,14 +330,18 @@ trait AstForFunctionsCreator(implicit withSchemaValidation: ValidationMode) { th

val typeFullName = param.paramType.map(_.name).getOrElse(Defines.Any)

val byRefCodePrefix = if (param.byRef) "&" else ""
val code = s"$byRefCodePrefix$$${param.name}"
val code = getParamCode(param)
val paramNode = parameterInNode(param, param.name, code, index, param.isVariadic, evaluationStrategy, typeFullName)
val attributeAsts = param.attributeGroups.flatMap(astForAttributeGroup)

Ast(paramNode).withChildren(attributeAsts)
}

private def getParamCode(param: PhpParam): String = {
val byRefCodePrefix = if (param.byRef) "&" else ""
s"$byRefCodePrefix$$${param.name}"
}

protected def astForStaticAndConstInits(node: PhpNode): Option[Ast] = {
scope.getConstAndStaticInits match {
case Nil => None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,12 @@ class Scope(summary: Map[String, Seq[SymbolSummary]] = Map.empty)
def surroundingMethodParams: List[String] =
stack.map(_.scopeNode).collectFirst { case ms: MethodScope => ms }.map(_.parameterNames.toList).get

def surroundingMethodReceiver: Option[String] =
stack
.collectFirst { case scopeEl @ ScopeElement(_: MethodScope, vars) => vars.headOption.map(_.head) }
.flatten
.filter(el => Set(NameConstants.StaticReceiver, NameConstants.This).contains(el))

def getConstAndStaticInits: List[PhpInit] = {
getInits(constAndStaticInits)
}
Expand Down
Loading
Loading