Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F1432451
PreOrderAnalysisVisitor.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
32 KB
Referenced Files
None
Subscribers
None
PreOrderAnalysisVisitor.php
View Options
<?php
declare
(
strict_types
=
1
);
namespace
Phan\Analysis
;
use
AssertionError
;
use
ast
;
use
ast\Node
;
use
Phan\AST\ArrowFunc
;
use
Phan\AST\ASTReverter
;
use
Phan\AST\ContextNode
;
use
Phan\AST\UnionTypeVisitor
;
use
Phan\BlockAnalysisVisitor
;
use
Phan\CodeBase
;
use
Phan\Config
;
use
Phan\Exception\CodeBaseException
;
use
Phan\Exception\RecursionDepthException
;
use
Phan\Exception\UnanalyzableException
;
use
Phan\Issue
;
use
Phan\IssueFixSuggester
;
use
Phan\Language\Context
;
use
Phan\Language\Element\Clazz
;
use
Phan\Language\Element\Func
;
use
Phan\Language\Element\FunctionInterface
;
use
Phan\Language\Element\Variable
;
use
Phan\Language\FQSEN\FullyQualifiedClassName
;
use
Phan\Language\FQSEN\FullyQualifiedFunctionName
;
use
Phan\Language\Scope\ClosureScope
;
use
Phan\Language\Type
;
use
Phan\Language\Type\ArrayShapeType
;
use
Phan\Language\Type\NullType
;
use
Phan\Language\Type\VoidType
;
use
Phan\Library\StringUtil
;
/**
* PreOrderAnalysisVisitor is where we do the pre-order part of the analysis
* during Phan's analysis phase.
*
* This is called in pre-order by BlockAnalysisVisitor
* (i.e. this is called before visiting all children of the current node)
*/
class
PreOrderAnalysisVisitor
extends
ScopeVisitor
{
/**
* @param CodeBase $code_base
* The code base in which we're analyzing code
*
* @param Context $context
* The context of the parser at the node for which we'd
* like to determine a type
*/
/*
public function __construct(
CodeBase $code_base,
Context $context
) {
parent::__construct($code_base, $context);
}
*/
/** @param Node $node implementation for unhandled nodes @unused-param */
public
function
visit
(
Node
$node
):
Context
{
return
$this
->
context
;
}
/**
* Visit a node with kind `ast\AST_CLASS`
*
* @param Node $node
* A node to parse
*
* @return Context
* A new or an unchanged context resulting from
* parsing the node
*
* @throws UnanalyzableException
* if the class name is unexpectedly empty
*
* @throws CodeBaseException
* if the class could not be located
*/
public
function
visitClass
(
Node
$node
):
Context
{
if
(
$node
->
flags
&
ast\flags\CLASS_ANONYMOUS
)
{
$class_name
=
(
new
ContextNode
(
$this
->
code_base
,
$this
->
context
,
$node
))->
getUnqualifiedNameForAnonymousClass
();
}
else
{
$class_name
=
(
string
)
$node
->
children
[
'name'
];
}
if
(!
StringUtil
::
isNonZeroLengthString
(
$class_name
))
{
// Should only occur with --use-fallback-parser
throw
new
UnanalyzableException
(
$node
,
"Class name cannot be empty"
);
}
$alternate_id
=
0
;
// Hunt for the alternate of this class defined
// in this file
do
{
// @phan-suppress-next-line PhanThrowTypeMismatchForCall
$class_fqsen
=
FullyQualifiedClassName
::
fromStringInContext
(
$class_name
,
$this
->
context
)->
withAlternateId
(
$alternate_id
++);
if
(!
$this
->
code_base
->
hasClassWithFQSEN
(
$class_fqsen
))
{
throw
new
CodeBaseException
(
$class_fqsen
,
"Can't find class {$class_fqsen} - aborting"
);
}
$clazz
=
$this
->
code_base
->
getClassByFQSEN
(
$class_fqsen
);
}
while
(
$this
->
context
->
getProjectRelativePath
()
!=
$clazz
->
getFileRef
()->
getProjectRelativePath
()
||
$node
->
children
[
'__declId'
]
!=
$clazz
->
getDeclId
()
||
$this
->
context
->
getLineNumberStart
()
!=
$clazz
->
getFileRef
()->
getLineNumberStart
()
);
return
$clazz
->
getContext
()->
withScope
(
$clazz
->
getInternalScope
()
)->
withoutLoops
();
}
/**
* Visit a node with kind `ast\AST_METHOD`
*
* @param Node $node
* A node to parse
*
* @return Context
* A new or an unchanged context resulting from
* parsing the node
*
* @throws CodeBaseException if the method could not be found
*/
public
function
visitMethod
(
Node
$node
):
Context
{
$method_name
=
(
string
)
$node
->
children
[
'name'
];
$code_base
=
$this
->
code_base
;
$context
=
$this
->
context
;
if
(!
$context
->
isInClassScope
())
{
throw
new
AssertionError
(
"Must be in class context to see a method"
);
}
$clazz
=
$this
->
getContextClass
();
if
(!
$clazz
->
hasMethodWithName
(
$code_base
,
$method_name
,
true
))
{
throw
new
CodeBaseException
(
null
,
"Can't find method {$clazz->getFQSEN()}::$method_name() - aborting"
);
}
$method
=
$clazz
->
getMethodByName
(
$code_base
,
$method_name
);
$method
->
ensureScopeInitialized
(
$code_base
);
// Fix #2504 - add flags to ensure that DimOffset warnings aren't emitted inside closures
Func
::
ensureDidAnnotate
(
$node
);
// Parse the comment above the method to get
// extra meta information about the method.
$comment
=
$method
->
getComment
();
$context
=
$this
->
context
->
withScope
(
clone
(
$method
->
getInternalScope
())
);
// For any @var references in the method declaration,
// add them as variables to the method's scope
if
(
$comment
!==
null
)
{
foreach
(
$comment
->
getVariableList
()
as
$parameter
)
{
$context
->
addScopeVariable
(
$parameter
->
asVariable
(
$this
->
context
)
);
}
}
// Add $this to the scope of non-static methods
if
(!(
$node
->
flags
&
ast\flags\MODIFIER_STATIC
))
{
if
(!
$clazz
->
getInternalScope
()->
hasVariableWithName
(
'this'
))
{
throw
new
AssertionError
(
"Classes must have a
\$
this variable."
);
}
$context
->
addScopeVariable
(
$clazz
->
getInternalScope
()->
getVariableByName
(
'this'
)
);
}
// Add each method parameter to the scope. We clone it
// so that changes to the variable don't alter the
// parameter definition
if
(
$method
->
getRecursionDepth
()
===
0
)
{
// Add each method parameter to the scope. We clone it
// so that changes to the variable don't alter the
// parameter definition
foreach
(
$method
->
getParameterList
()
as
$parameter
)
{
$context
->
addScopeVariable
(
$parameter
->
cloneAsNonVariadic
()
);
}
}
if
(
$method
->
getName
()
===
'__construct'
&&
Config
::
getValue
(
'infer_default_properties_in_construct'
)
&&
$clazz
->
isClass
()
&&
!
$method
->
isAbstract
())
{
$this
->
addDefaultPropertiesOfThisToContext
(
$clazz
,
$context
);
}
// TODO: Why is the check for yield in PreOrderAnalysisVisitor?
if
(
$method
->
hasYield
())
{
$this
->
setReturnTypeOfGenerator
(
$method
,
$node
);
}
return
$context
;
}
/**
* Modifies the context of $class in place, adding types of default values for all declared properties
*/
private
function
addDefaultPropertiesOfThisToContext
(
Clazz
$class
,
Context
$context
):
void
{
$property_types
=
[];
foreach
(
$class
->
getPropertyMap
(
$this
->
code_base
)
as
$property
)
{
if
(
$property
->
isDynamicOrFromPHPDoc
())
{
continue
;
}
if
(
$property
->
isStatic
())
{
continue
;
}
$default_type
=
$property
->
getDefaultType
();
if
(!
$default_type
)
{
continue
;
}
if
(
$property
->
getFQSEN
()
!==
$property
->
getRealDefiningFQSEN
())
{
// Here, we don't analyze the properties of parent classes to avoid false positives.
// Phan doesn't infer that the scope is cleared by parent::__construct().
//
// TODO: It should be possible to inherit property types from parent::__construct() for simple constructors?
// TODO: Check if there's actually any calls to parent::__construct, infer types aggressively if there are no calls.
// TODO: Phan does not yet infer or apply implications of setPropName(), etc.
continue
;
}
$property_types
[
$property
->
getName
()]
=
$default_type
;
}
if
(!
$property_types
)
{
return
;
}
$override_type
=
ArrayShapeType
::
fromFieldTypes
(
$property_types
,
false
);
$variable
=
new
Variable
(
$context
,
Context
::
VAR_NAME_THIS_PROPERTIES
,
$override_type
->
asPHPDocUnionType
(),
0
);
$context
->
addScopeVariable
(
$variable
);
}
/**
* Visit a node with kind `ast\AST_FUNC_DECL`
*
* @param Node $node
* A node to parse
*
* @return Context
* A new or an unchanged context resulting from
* parsing the node
* @throws CodeBaseException
* if this function declaration could not be found
*/
public
function
visitFuncDecl
(
Node
$node
):
Context
{
$function_name
=
(
string
)
$node
->
children
[
'name'
];
$code_base
=
$this
->
code_base
;
$original_context
=
$this
->
context
;
// This really ought not to throw given that
// we already successfully parsed the code
// base (the AST names should be valid)
// @phan-suppress-next-line PhanThrowTypeMismatchForCall
$canonical_function
=
(
new
ContextNode
(
$code_base
,
$original_context
,
$node
))->
getFunction
(
$function_name
,
true
);
// Hunt for the alternate associated with the file we're
// looking at currently in this context.
$function
=
null
;
foreach
(
$canonical_function
->
alternateGenerator
(
$code_base
)
as
$alternate_function
)
{
if
(
$alternate_function
->
getFileRef
()->
getProjectRelativePath
()
===
$original_context
->
getProjectRelativePath
()
)
{
$function
=
$alternate_function
;
break
;
}
}
if
(!(
$function
instanceof
Func
))
{
// No alternate was found
throw
new
CodeBaseException
(
null
,
"Can't find function {$function_name} in context {$this->context} - aborting"
);
}
$function
->
ensureScopeInitialized
(
$code_base
);
// Fix #2504 - add flags to ensure that DimOffset warnings aren't emitted inside closures
Func
::
ensureDidAnnotate
(
$node
);
$context
=
$original_context
->
withScope
(
clone
(
$function
->
getInternalScope
())
)->
withoutLoops
();
// Parse the comment above the function to get
// extra meta information about the function.
// TODO: Investigate caching information from Comment::fromStringInContext?
$comment
=
$function
->
getComment
();
// For any @var references in the function declaration,
// add them as variables to the function's scope
if
(
$comment
!==
null
)
{
foreach
(
$comment
->
getVariableList
()
as
$parameter
)
{
$context
->
addScopeVariable
(
$parameter
->
asVariable
(
$this
->
context
)
);
}
}
if
(
$function
->
getRecursionDepth
()
===
0
)
{
// Add each method parameter to the scope. We clone it
// so that changes to the variable don't alter the
// parameter definition
foreach
(
$function
->
getParameterList
()
as
$parameter
)
{
$context
->
addScopeVariable
(
$parameter
->
cloneAsNonVariadic
()
);
}
}
if
(
$function
->
hasYield
())
{
$this
->
setReturnTypeOfGenerator
(
$function
,
$node
);
}
if
(!
$function
->
hasReturn
()
&&
$function
->
getUnionType
()->
isEmpty
())
{
// TODO: This is a global function - also guarantee that it's a real type elsewhere if phpdoc matches the implementation.
$function
->
setUnionType
(
VoidType
::
instance
(
false
)->
asRealUnionType
());
}
return
$context
;
}
private
static
function
getOverrideClassFQSEN
(
CodeBase
$code_base
,
Func
$func
):
?
FullyQualifiedClassName
{
$closure_scope
=
$func
->
getInternalScope
();
if
(
$closure_scope
instanceof
ClosureScope
)
{
$class_fqsen
=
$closure_scope
->
getOverrideClassFQSEN
();
if
(!
$class_fqsen
)
{
return
null
;
}
// Postponed the check for undeclared closure scopes to the analysis phase,
// because classes are still being parsed in the parse phase.
if
(!
$code_base
->
hasClassWithFQSEN
(
$class_fqsen
))
{
$func_context
=
$func
->
getContext
();
Issue
::
maybeEmit
(
$code_base
,
$func_context
,
Issue
::
UndeclaredClosureScope
,
$func_context
->
getLineNumberStart
(),
(
string
)
$class_fqsen
);
// Avoid an uncaught CodeBaseException due to missing class for @phan-closure-scope
// Just pretend it's the containing class instead of the missing class.
$closure_scope
->
overrideClassFQSEN
(
$func_context
->
getScope
()->
getParentScope
()->
getClassFQSENOrNull
());
return
null
;
}
return
$class_fqsen
;
}
return
null
;
}
/**
* If a Closure overrides the scope(class) it will be executed in (via doc comment)
* then return a context with the new scope instead.
*/
private
static
function
addThisVariableToInternalScope
(
CodeBase
$code_base
,
Context
$context
,
Func
$func
):
void
{
// skip adding $this to internal scope if the closure is a static one
if
(
$func
->
getFlags
()
===
ast\flags\MODIFIER_STATIC
)
{
return
;
}
$override_this_fqsen
=
self
::
getOverrideClassFQSEN
(
$code_base
,
$func
);
if
(
$override_this_fqsen
!==
null
)
{
if
(
$context
->
getScope
()->
hasVariableWithName
(
'this'
)
||
!
$context
->
isInClassScope
())
{
// Handle @phan-closure-scope - Should set $this to the overridden class, as well as handling self:: and parent::
$func
->
getInternalScope
()->
addVariable
(
new
Variable
(
$context
,
'this'
,
$override_this_fqsen
->
asRealUnionType
(),
0
)
);
}
return
;
}
// If we have a 'this' variable in our current scope,
// pass it down into the closure
if
(
$context
->
getScope
()->
hasVariableWithName
(
'this'
))
{
// Normal case: Closures inherit $this from parent scope.
$this_var_from_scope
=
$context
->
getScope
()->
getVariableByName
(
'this'
);
$func
->
getInternalScope
()->
addVariable
(
$this_var_from_scope
);
}
}
/**
* 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
{
$code_base
=
$this
->
code_base
;
$context
=
$this
->
context
->
withoutLoops
();
$closure_fqsen
=
FullyQualifiedFunctionName
::
fromClosureInContext
(
$context
->
withLineNumberStart
(
$node
->
lineno
),
$node
);
$func
=
$code_base
->
getFunctionByFQSEN
(
$closure_fqsen
);
$func
->
ensureScopeInitialized
(
$code_base
);
// Fix #2504 - add flags to ensure that DimOffset warnings aren't emitted inside closures
Func
::
ensureDidAnnotate
(
$node
);
// If we have a 'this' variable in our current scope,
// pass it down into the closure
self
::
addThisVariableToInternalScope
(
$code_base
,
$context
,
$func
);
// Make the closure reachable by FQSEN from anywhere
$code_base
->
addFunction
(
$func
);
if
((
$node
->
children
[
'uses'
]->
kind
??
null
)
===
ast\AST_CLOSURE_USES
)
{
foreach
(
$node
->
children
[
'uses'
]->
children
??
[]
as
$use
)
{
if
(!(
$use
instanceof
Node
)
||
$use
->
kind
!==
ast\AST_CLOSURE_VAR
)
{
$this
->
emitIssue
(
Issue
::
VariableUseClause
,
$node
->
lineno
,
ASTReverter
::
toShortString
(
$use
)
);
continue
;
}
$variable_name
=
(
new
ContextNode
(
$code_base
,
$context
,
$use
->
children
[
'name'
]
))->
getVariableName
();
// TODO: Distinguish between the empty string and the lack of a name
if
(
$variable_name
===
''
)
{
continue
;
}
if
(
$variable_name
===
'this'
)
{
$this
->
emitIssue
(
Issue
::
InvalidNode
,
$use
->
lineno
,
'Cannot use $this as a lexical variable'
);
continue
;
}
elseif
(
Variable
::
isSuperglobalVariableWithName
(
$variable_name
))
{
Issue
::
maybeEmit
(
$code_base
,
$context
,
Issue
::
InvalidNode
,
$node
->
lineno
,
"Cannot use auto-global
\$
$variable_name as lexical variable"
);
}
// Check to see if the variable exists in this scope
if
(!
$context
->
getScope
()->
hasVariableWithName
(
$variable_name
))
{
// If this is not pass-by-reference variable we
// have a problem
if
(!(
$use
->
flags
&
ast\flags\CLOSURE_USE_REF
))
{
Issue
::
maybeEmitWithParameters
(
$this
->
code_base
,
$context
,
Issue
::
UndeclaredVariable
,
$use
->
lineno
,
[
$variable_name
],
IssueFixSuggester
::
suggestVariableTypoFix
(
$this
->
code_base
,
$context
,
$variable_name
)
);
$variable
=
new
Variable
(
$context
,
$variable_name
,
NullType
::
instance
(
false
)->
asPHPDocUnionType
(),
0
);
}
else
{
// If the variable doesn't exist, but it's
// a pass-by-reference variable, we can
// just create it
$variable
=
Variable
::
fromNodeInContext
(
$use
,
$context
,
$this
->
code_base
,
false
);
}
// And add it to the scope of the parent (For https://github.com/phan/phan/issues/367)
$context
->
addScopeVariable
(
$variable
);
}
else
{
$variable
=
$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\CLOSURE_USE_REF
))
{
$variable
=
clone
(
$variable
);
}
else
{
$union_type
=
$variable
->
getUnionType
();
if
(
$union_type
->
hasRealTypeSet
())
{
$variable
->
setUnionType
(
$union_type
->
eraseRealTypeSetRecursively
());
}
}
}
// Pass the variable into a new scope
$func
->
getInternalScope
()->
addVariable
(
$variable
);
}
}
if
(!
$func
->
hasReturn
()
&&
$func
->
getUnionType
()->
isEmpty
())
{
$func
->
setUnionType
(
VoidType
::
instance
(
false
)->
asRealUnionType
());
}
// Add parameters to the context.
$context
=
$context
->
withScope
(
clone
(
$func
->
getInternalScope
()));
$comment
=
$func
->
getComment
();
// For any @var references in the method declaration,
// add them as variables to the method's scope
if
(
$comment
!==
null
)
{
foreach
(
$comment
->
getVariableList
()
as
$parameter
)
{
$context
->
addScopeVariable
(
$parameter
->
asVariable
(
$this
->
context
)
);
}
}
if
(
$func
->
getRecursionDepth
()
===
0
)
{
// Add each closure parameter to the scope. We clone it
// so that changes to the variable don't alter the
// parameter definition
foreach
(
$func
->
getParameterList
()
as
$parameter
)
{
$context
->
addScopeVariable
(
$parameter
->
cloneAsNonVariadic
()
);
}
}
if
(
$func
->
hasYield
())
{
$this
->
setReturnTypeOfGenerator
(
$func
,
$node
);
}
return
$context
;
}
/**
* Visit a node with kind `ast\AST_ARROW_FUNC`
*
* @param Node $node
* A node to parse
*
* @return Context
* A new or an unchanged context resulting from
* parsing the node
* @override
*/
public
function
visitArrowFunc
(
Node
$node
):
Context
{
$code_base
=
$this
->
code_base
;
$context
=
$this
->
context
->
withoutLoops
();
$closure_fqsen
=
FullyQualifiedFunctionName
::
fromClosureInContext
(
$context
->
withLineNumberStart
(
$node
->
lineno
),
$node
);
$func
=
$code_base
->
getFunctionByFQSEN
(
$closure_fqsen
);
$func
->
ensureScopeInitialized
(
$code_base
);
// Fix #2504 - add flags to ensure that DimOffset warnings aren't emitted inside closures
Func
::
ensureDidAnnotate
(
$node
);
// If we have a 'this' variable in our current scope,
// pass it down into the closure
self
::
addThisVariableToInternalScope
(
$code_base
,
$context
,
$func
);
// Make the closure reachable by FQSEN from anywhere
$code_base
->
addFunction
(
$func
);
foreach
(
ArrowFunc
::
getUses
(
$node
)
as
$variable_name
=>
$use
)
{
$variable_name
=
(
string
)
$variable_name
;
// Check to see if the variable exists in this scope
// (If it doesn't, then don't add it - Phan will check later if it properly declares the variable in the scope.)
if
(
$context
->
getScope
()->
hasVariableWithName
(
$variable_name
))
{
ArrowFunc
::
recordVariableExistsInOuterScope
(
$node
,
$variable_name
);
$variable
=
$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\CLOSURE_USE_REF
))
{
$variable
=
clone
(
$variable
);
}
// Pass the variable into a new scope
$func
->
getInternalScope
()->
addVariable
(
$variable
);
}
}
if
(!
$func
->
hasReturn
()
&&
$func
->
getUnionType
()->
isEmpty
())
{
$func
->
setUnionType
(
VoidType
::
instance
(
false
)->
asRealUnionType
());
}
// Add parameters to the context.
$context
=
$context
->
withScope
(
clone
(
$func
->
getInternalScope
()));
$comment
=
$func
->
getComment
();
// For any @var references in the method declaration,
// add them as variables to the method's scope
if
(
$comment
!==
null
)
{
foreach
(
$comment
->
getVariableList
()
as
$parameter
)
{
$context
->
addScopeVariable
(
$parameter
->
asVariable
(
$this
->
context
)
);
}
}
if
(
$func
->
getRecursionDepth
()
===
0
)
{
// Add each closure parameter to the scope. We clone it
// so that changes to the variable don't alter the
// parameter definition
foreach
(
$func
->
getParameterList
()
as
$parameter
)
{
$context
->
addScopeVariable
(
$parameter
->
cloneAsNonVariadic
()
);
}
}
if
(
$func
->
hasYield
())
{
$this
->
setReturnTypeOfGenerator
(
$func
,
$node
);
}
return
$context
;
}
/**
* The return type of the given FunctionInterface to a Generator.
* Emit an Issue if the documented return type is incompatible with that.
*/
private
function
setReturnTypeOfGenerator
(
FunctionInterface
$func
,
Node
$node
):
void
{
// Currently, there is no way to describe the types passed to
// a Generator in phpdoc.
// So, nothing bothers recording the types beyond \Generator.
$func
->
setHasReturn
(
true
);
// Returns \Generator, technically
$func
->
setHasYield
(
true
);
if
(
$func
->
getUnionType
()->
isEmpty
())
{
$func
->
setIsReturnTypeUndefined
(
true
);
$func
->
setUnionType
(
$func
->
getUnionType
()->
withType
(
Type
::
fromNamespaceAndName
(
'
\\
'
,
'Generator'
,
false
)));
}
if
(!
$func
->
isReturnTypeUndefined
())
{
$func_return_type
=
$func
->
getUnionType
();
try
{
$func_return_type_can_cast
=
Type
::
fromNamespaceAndName
(
'
\\
'
,
'Generator'
,
false
)->
asPHPDocUnionType
()->
canCastToUnionType
(
$func_return_type
,
$this
->
code_base
);
}
catch
(
RecursionDepthException
$_
)
{
return
;
}
if
(!
$func_return_type_can_cast
)
{
// At least one of the documented return types must
// be Generator, Iterable, or Traversable.
// Check for the issue here instead of in visitReturn/visitYield so that
// the check is done exactly once.
$this
->
emitIssue
(
Issue
::
TypeMismatchReturn
,
$node
->
lineno
,
'(a Generator due to existence of yield statements)'
,
'
\\
Generator'
,
$func
->
getNameForIssue
(),
(
string
)
$func_return_type
);
}
}
}
/**
* @param Node $node
* A node to parse
*
* @return Context
* An unchanged context resulting from parsing the node
*/
public
function
visitAssign
(
Node
$node
):
Context
{
if
(
Config
::
get_closest_minimum_target_php_version_id
()
<
70100
)
{
$var_node
=
$node
->
children
[
'var'
];
if
(
$var_node
instanceof
Node
&&
$var_node
->
kind
===
ast\AST_ARRAY
)
{
BlockAnalysisVisitor
::
analyzeArrayAssignBackwardsCompatibility
(
$this
->
code_base
,
$this
->
context
,
$var_node
);
}
}
return
$this
->
context
;
}
/**
* No-op - all work is done in BlockAnalysisVisitor
*
* @param Node $node @unused-param
* A node to parse
*
* @return Context
* A new or an unchanged context resulting from
* parsing the node
*/
public
function
visitForeach
(
Node
$node
):
Context
{
return
$this
->
context
;
}
/**
* @param Node $node
* A node to parse
*
* @return Context
* A new or an unchanged context resulting from
* parsing the node
*/
public
function
visitCatch
(
Node
$node
):
Context
{
// @phan-suppress-next-line PhanThrowTypeAbsentForCall
$union_type
=
UnionTypeVisitor
::
unionTypeFromClassNode
(
$this
->
code_base
,
$this
->
context
,
$node
->
children
[
'class'
]
);
if
(!
isset
(
$node
->
children
[
'var'
]))
{
if
(
Config
::
get_closest_minimum_target_php_version_id
()
<
80000
)
{
$this
->
emitIssue
(
Issue
::
CompatibleNonCapturingCatch
,
$node
->
lineno
,
ASTReverter
::
toShortString
(
$node
->
children
[
'class'
])
);
}
}
try
{
$class_list
=
\iterator_to_array
(
$union_type
->
asClassList
(
$this
->
code_base
,
$this
->
context
));
if
(
Config
::
get_closest_minimum_target_php_version_id
()
<
70100
&&
\count
(
$class_list
)
>
1
)
{
$this
->
emitIssue
(
Issue
::
CompatibleMultiExceptionCatchPHP70
,
$node
->
lineno
);
}
foreach
(
$class_list
as
$class
)
{
$class
->
addReference
(
$this
->
context
);
}
}
catch
(
CodeBaseException
$exception
)
{
Issue
::
maybeEmitWithParameters
(
$this
->
code_base
,
$this
->
context
,
Issue
::
UndeclaredClassCatch
,
$node
->
lineno
,
[(
string
)
$exception
->
getFQSEN
()],
IssueFixSuggester
::
suggestSimilarClassForGenericFQSEN
(
$this
->
code_base
,
$this
->
context
,
$exception
->
getFQSEN
())
);
}
$var_node
=
$node
->
children
[
'var'
];
if
(!
$var_node
instanceof
Node
)
{
// The catch variable is optional in newer php versions
return
$this
->
context
;
}
// Calculate the intersection type of the class statement
// (E.g. MyInterface&Throwable)
$union_type
=
ConditionVisitor
::
calculateNarrowedUnionType
(
$this
->
code_base
,
$this
->
context
,
$union_type
,
Type
::
throwableInstance
()->
asPHPDocUnionType
()
);
$variable_name
=
(
new
ContextNode
(
$this
->
code_base
,
$this
->
context
,
$var_node
))->
getVariableName
();
if
(
$variable_name
!==
''
)
{
$variable
=
Variable
::
fromNodeInContext
(
$var_node
,
$this
->
context
,
$this
->
code_base
,
false
);
if
(!
$union_type
->
isEmpty
())
{
$variable
->
setUnionType
(
$union_type
);
}
$this
->
context
->
addScopeVariable
(
$variable
);
}
return
$this
->
context
;
}
/**
* @param Node $node
* A node to parse
*
* @return Context
* A new or an unchanged context resulting from
* parsing the node
*/
public
function
visitIfElem
(
Node
$node
):
Context
{
$cond
=
$node
->
children
[
'cond'
]
??
null
;
if
(!(
$cond
instanceof
Node
))
{
return
$this
->
context
;
}
// Look to see if any proofs we do within the condition
// can say anything about types within the statement
// list.
return
(
new
ConditionVisitor
(
$this
->
code_base
,
$this
->
context
))->
__invoke
(
$cond
);
}
// visitWhile is unnecessary, this has special logic in BlockAnalysisVisitor to handle conditions assigning variables to the loop
/**
* @param Node $node
* A node to parse
*
* @return Context
* A new or an unchanged context resulting from
* parsing the node
*/
public
function
visitFor
(
Node
$node
):
Context
{
$cond
=
$node
->
children
[
'cond'
];
if
(!(
$cond
instanceof
Node
))
{
return
$this
->
context
;
}
// Look to see if any proofs we do within the condition of the while
// can say anything about types within the statement
// list.
return
(
new
ConditionVisitor
(
$this
->
code_base
,
$this
->
context
))->
__invoke
(
$cond
);
}
/**
* @param Node $node @unused-param
* A node to parse
*
* @return Context
* A new or an unchanged context resulting from
* parsing the node
*/
public
function
visitCall
(
Node
$node
):
Context
{
return
$this
->
context
;
}
/**
* @return Clazz
* Get the class on this scope or fail real hard
*/
private
function
getContextClass
():
Clazz
{
return
$this
->
context
->
getClassInScope
(
$this
->
code_base
);
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Sat, May 16, 21:46 (1 d, 5 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
44/68/3f8fcae3879824d36a9f2d35e692
Default Alt Text
PreOrderAnalysisVisitor.php (32 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment