/** * @param Node $node * A node to parse * * @return Context * A new or an unchanged context resulting from * parsing the node */ public function visitClosure(Node $node) : Context { $this->analyzeNoOp($node, "no-op closure"); return $this->context->withClosureFQSEN(FullyQualifiedFunctionName::fromClosureInContext($this->context)); }
/** * Visit a node with kind `\ast\AST_CLOSURE` * * @param Node $node * A node of the type indicated by the method name that we'd * like to figure out the type that it produces. * * @return UnionType * The set of types that are possibly produced by the * given node */ public function visitClosure(Decl $node) : UnionType { // The type of a closure is the fqsen pointing // at its definition $closure_fqsen = FullyQualifiedFunctionName::fromClosureInContext($this->context); $type = CallableType::instanceWithClosureFQSEN($closure_fqsen)->asUnionType(); return $type; }
/** * @return Method */ public function getClosure() : Func { $closure_fqsen = FullyQualifiedFunctionName::fromClosureInContext($this->context); if (!$this->code_base->hasFunctionWithFQSEN($closure_fqsen)) { throw new CodeBaseException($closure_fqsen, "Could not find closure {$closure_fqsen}"); } return $this->code_base->getFunctionByFQSEN($closure_fqsen); }
/** * Visit a node with kind `\ast\AST_CLOSURE` * * @param Node $node * A node to parse * * @return Context * A new or an unchanged context resulting from * parsing the node */ public function visitClosure(Node $node) : Context { $closure_fqsen = FullyQualifiedFunctionName::fromClosureInContext($this->context); $method = Method::fromNode($this->context, $this->code_base, $node); // Override the FQSEN with the found alternate ID $method->setFQSEN($closure_fqsen); // Make the closure reachable by FQSEN from anywhere $this->code_base->addMethod($method); // If we have a 'this' variable in our current scope, // pass it down into the closure $context = $this->context->withScope(new Scope()); if ($context->getScope()->hasVariableWithName('this')) { $context = $context->addScopeVariable($this->context->getScope()->getVariableWithName('this')); } if (!empty($node->children['uses']) && $node->children['uses']->kind == \ast\AST_CLOSURE_USES) { $uses = $node->children['uses']; foreach ($uses->children as $use) { if ($use->kind != \ast\AST_CLOSURE_VAR) { Log::err(Log::EVAR, "You can only have variables in a closure use() clause", $this->context->getFile(), $node->lineno); continue; } $variable_name = AST::variableName($use->children['name']); if (empty($variable_name)) { continue; } $variable = null; // Check to see if the variable exists in this scope if (!$this->context->getScope()->hasVariableWithName($variable_name)) { // If this is not pass-by-reference variable we // have a problem if (!($use->flags & \ast\flags\PARAM_REF)) { Log::err(Log::EVAR, "Variable \${$variable_name} is not defined", $this->context->getFile(), $node->lineno); continue; } else { // If the variable doesn't exist, but its // a pass-by-reference variable, we can // just create it $variable = Variable::fromNodeInContext($use, $this->context, $this->code_base, false); } } else { $variable = $this->context->getScope()->getVariableWithName($variable_name); // If this isn't a pass-by-reference variable, we // clone the variable so state within this scope // doesn't update the outer scope if (!($use->flags & \ast\flags\PARAM_REF)) { $variable = clone $variable; } } // Pass the variable into a new scope $context = $context->withScopeVariable($variable); } } // Add all parameters to the scope if (!empty($node->children['params']) && $node->children['params']->kind == \ast\AST_PARAM_LIST) { $params = $node->children['params']; foreach ($params->children as $param) { // Read the parameter $parameter = Parameter::fromNode($this->context, $this->code_base, $param); // Add it to the scope $context = $context->withScopeVariable($parameter); } } return $context->withClosureFQSEN($closure_fqsen); }
/** * Visit a node with kind `\ast\AST_CLOSURE` * * @param Node $node * A node to parse * * @return Context * A new or an unchanged context resulting from * parsing the node */ public function visitClosure(Decl $node) : Context { $closure_fqsen = FullyQualifiedFunctionName::fromClosureInContext($this->context->withLineNumberStart($node->lineno ?? 0)); $func = Func::fromNode($this->context, $this->code_base, $node, $closure_fqsen); // If we have a 'this' variable in our current scope, // pass it down into the closure if ($this->context->getScope()->hasVariableWithName('this')) { $func->getInternalScope()->addVariable($this->context->getScope()->getVariableByName('this')); } // Make the closure reachable by FQSEN from anywhere $this->code_base->addFunction($func); if (!empty($node->children['uses']) && $node->children['uses']->kind == \ast\AST_CLOSURE_USES) { $uses = $node->children['uses']; foreach ($uses->children as $use) { if ($use->kind != \ast\AST_CLOSURE_VAR) { $this->emitIssue(Issue::VariableUseClause, $node->lineno ?? 0); continue; } $variable_name = (new ContextNode($this->code_base, $this->context, $use->children['name']))->getVariableName(); if (empty($variable_name)) { continue; } $variable = null; // Check to see if the variable exists in this scope if (!$this->context->getScope()->hasVariableWithName($variable_name)) { // If this is not pass-by-reference variable we // have a problem if (!($use->flags & \ast\flags\PARAM_REF)) { $this->emitIssue(Issue::UndeclaredVariable, $node->lineno ?? 0, $variable_name); continue; } else { // If the variable doesn't exist, but its // a pass-by-reference variable, we can // just create it $variable = Variable::fromNodeInContext($use, $this->context, $this->code_base, false); } } else { $variable = $this->context->getScope()->getVariableByName($variable_name); // If this isn't a pass-by-reference variable, we // clone the variable so state within this scope // doesn't update the outer scope if (!($use->flags & \ast\flags\PARAM_REF)) { $variable = clone $variable; } } // Pass the variable into a new scope $func->getInternalScope()->addVariable($variable); } } // Add all parameters to the scope if (!empty($node->children['params']) && $node->children['params']->kind == \ast\AST_PARAM_LIST) { $params = $node->children['params']; foreach ($params->children as $param) { // Read the parameter $parameter = Parameter::fromNode($this->context, $this->code_base, $param); // Add it to the scope $func->getInternalScope()->addVariable($parameter); } } if ($this->analyzeFunctionLikeIsGenerator($node)) { $this->setReturnTypeOfGenerator($func, $node); } return $this->context->withScope($func->getInternalScope()); }