diff --git a/config.xsd b/config.xsd index 4c1d0786c..7075ab368 100644 --- a/config.xsd +++ b/config.xsd @@ -18,6 +18,7 @@ + @@ -431,4 +432,15 @@ + + + + + + + + + + + diff --git a/src/Psalm/Config.php b/src/Psalm/Config.php index d73deddb4..ef95dbc99 100644 --- a/src/Psalm/Config.php +++ b/src/Psalm/Config.php @@ -346,6 +346,11 @@ class Config /** @var string|null */ public $error_baseline = null; + /** + * @var array + */ + public $globals = []; + protected function __construct() { self::$instance = $this; @@ -760,6 +765,13 @@ class Config } } + if (isset($config_xml->globals) && isset($config_xml->globals->var)) { + /** @var \SimpleXMLElement $var */ + foreach ($config_xml->globals->var as $var) { + $config->globals[(string) $var['name']] = (string) $var['type']; + } + } + return $config; } diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php index 53c2351da..31cb878ca 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php @@ -110,6 +110,20 @@ class VariableFetchAnalyzer return null; } + if ($context->is_global && is_string($stmt->name)) { + $var_name = '$' . $stmt->name; + + if (!$context->hasVariable($var_name, $statements_analyzer)) { + $type = $statements_analyzer->getGlobalType($stmt->name); + if ($type) { + $context->vars_in_scope[$var_name] = $type; + $context->vars_possibly_in_scope[$var_name] = true; + $stmt->inferredType = clone $type; + return null; + } + } + } + if (in_array( $stmt->name, [ @@ -140,27 +154,6 @@ class VariableFetchAnalyzer return null; } - if ($context->is_global && ($stmt->name === 'argv' || $stmt->name === 'argc')) { - $var_name = '$' . $stmt->name; - - if (!$context->hasVariable($var_name, $statements_analyzer)) { - if ($stmt->name === 'argv') { - $context->vars_in_scope[$var_name] = new Type\Union([ - new Type\Atomic\TArray([ - Type::getInt(), - Type::getString(), - ]), - ]); - } else { - $context->vars_in_scope[$var_name] = Type::getInt(); - } - } - - $context->vars_possibly_in_scope[$var_name] = true; - $stmt->inferredType = clone $context->vars_in_scope[$var_name]; - return null; - } - if (!is_string($stmt->name)) { return ExpressionAnalyzer::analyze($statements_analyzer, $stmt->name, $context); } diff --git a/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php b/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php index b81070ae4..9da9e54a0 100644 --- a/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php @@ -1530,4 +1530,26 @@ class StatementsAnalyzer extends SourceAnalyzer implements StatementsSource return $const_name; } + + /** + * @return Type\Union|null + */ + public function getGlobalType(string $name) + { + $config = Config::getInstance(); + + if (isset($config->globals[$name])) { + return Type::parseString($config->globals[$name]); + } + + if ($name === 'argv') { + return new Type\Union([ + new Type\Atomic\TArray([Type::getInt(), Type::getString()]), + ]); + } + + if ($name === 'argc') { + return Type::getInt(); + } + } } diff --git a/tests/ConfigTest.php b/tests/ConfigTest.php index 69082bf92..5f28b7da7 100644 --- a/tests/ConfigTest.php +++ b/tests/ConfigTest.php @@ -1117,4 +1117,76 @@ class ConfigTest extends TestCase ); } } + + /** + * @return void + */ + public function testGlobals() + { + $this->project_analyzer = $this->getProjectAnalyzerWithConfig( + TestConfig::loadFromXML( + dirname(__DIR__), + ' + + + + + + + + ' + ) + ); + + $file_path = getcwd() . '/src/somefile.php'; + + $this->addFile( + $file_path, + 'func(); + + assert($glob4 !== null); + ord($glob4); + + function example1(): void { + global $glob1, $glob2, $glob3, $glob4; + ord($glob1); + ord($glob2["str"]); + $glob3->func(); + ord($glob4); + } + + $glob1 = 0; + error_reporting($glob1); + + function example2(): void { + global $glob1, $glob2, $glob3; + error_reporting($glob1); + ord($glob2["str"]); + $glob3->func(); + } + } + namespace ns { + ord($glob1); + ord($glob2["str"]); + $glob3->func(); + + class Clazz { + public function func(): void {} + } + + function example3(): void { + global $glob1, $glob2, $glob3; + ord($glob1); + ord($glob2["str"]); + $glob3->func(); + } + }' + ); + + $this->analyzeFile($file_path, new Context()); + } }