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());
+ }
}