diff --git a/config.xsd b/config.xsd
index 2668b8252..3083e5931 100644
--- a/config.xsd
+++ b/config.xsd
@@ -68,6 +68,7 @@
+
diff --git a/docs/running_psalm/configuration.md b/docs/running_psalm/configuration.md
index 4288cc77c..9fae1d4de 100644
--- a/docs/running_psalm/configuration.md
+++ b/docs/running_psalm/configuration.md
@@ -275,6 +275,17 @@ Set the php version psalm should assume when checking and/or fixing the project.
This can be overridden on the command-line using the `--php-version=` flag which takes the highest precedence over both the `phpVersion` setting and the version derived from `composer.json`.
+#### skipChecksOnUnresolvableIncludes
+```xml
+
+```
+
+When `true`, Psalm will skip checking classes, variables and functions after it comes across an `include` or `require` it cannot resolve. This allows code to reference functions and classes unknown to Psalm.
+
+For backwards compatibility, this defaults to `true`, but if you do not rely on dynamically generated includes to cause classes otherwise unknown to psalm to come into existence, it's recommended you set this to `false` in order to reliably detect errors that would be fatal to PHP at runtime.
+
### Running Psalm
#### autoloader
diff --git a/src/Psalm/Config.php b/src/Psalm/Config.php
index 2e3a8f3d4..7ac577b48 100644
--- a/src/Psalm/Config.php
+++ b/src/Psalm/Config.php
@@ -291,6 +291,11 @@ class Config
*/
public $use_phpdoc_property_without_magic_or_parent = false;
+ /**
+ * @var bool
+ */
+ public $skip_checks_on_unresolvable_includes = true;
+
/**
* @var bool
*/
@@ -765,6 +770,7 @@ class Config
'ensureArrayStringOffsetsExist' => 'ensure_array_string_offsets_exist',
'ensureArrayIntOffsetsExist' => 'ensure_array_int_offsets_exist',
'reportMixedIssues' => 'show_mixed_issues',
+ 'skipChecksOnUnresolvableIncludes' => 'skip_checks_on_unresolvable_includes'
];
foreach ($booleanAttributes as $xmlName => $internalName) {
diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/IncludeAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/IncludeAnalyzer.php
index 0a452364a..f08f69079 100644
--- a/src/Psalm/Internal/Analyzer/Statements/Expression/IncludeAnalyzer.php
+++ b/src/Psalm/Internal/Analyzer/Statements/Expression/IncludeAnalyzer.php
@@ -160,6 +160,11 @@ class IncludeAnalyzer
$global_context
);
} catch (\Psalm\Exception\UnpreparedAnalysisException $e) {
+ if ($config->skip_checks_on_unresolvable_includes) {
+ $context->check_classes = false;
+ $context->check_variables = false;
+ $context->check_functions = false;
+ }
}
foreach ($include_file_analyzer->getRequiredFilePaths() as $required_file_path) {
@@ -200,6 +205,12 @@ class IncludeAnalyzer
}
}
+ if ($config->skip_checks_on_unresolvable_includes) {
+ $context->check_classes = false;
+ $context->check_variables = false;
+ $context->check_functions = false;
+ }
+
return null;
}
diff --git a/tests/IncludeTest.php b/tests/IncludeTest.php
index 3c7a0d3c9..d63a99639 100644
--- a/tests/IncludeTest.php
+++ b/tests/IncludeTest.php
@@ -37,6 +37,7 @@ class IncludeTest extends TestCase
}
$config = $codebase->config;
+ $config->skip_checks_on_unresolvable_includes = true;
foreach ($error_levels as $error_level) {
$config->setCustomErrorLevel($error_level, \Psalm\Config::REPORT_SUPPRESS);
@@ -83,6 +84,7 @@ class IncludeTest extends TestCase
}
$config = $codebase->config;
+ $config->skip_checks_on_unresolvable_includes = false;
$this->expectException(\Psalm\Exception\CodeException::class);
$this->expectExceptionMessageRegExp('/\b' . preg_quote($error_message, '/') . '\b/');
@@ -569,6 +571,25 @@ class IncludeTest extends TestCase
getcwd() . DIRECTORY_SEPARATOR . 'a' . DIRECTORY_SEPARATOR . 'b' . DIRECTORY_SEPARATOR . 'c' . DIRECTORY_SEPARATOR . 'd' . DIRECTORY_SEPARATOR . 'script.php',
],
],
+ 'undefinedMethodAfterInvalidRequire' => [
+ 'files' => [
+ getcwd() . DIRECTORY_SEPARATOR . 'file2.php' => ' ' [
+ getcwd() . DIRECTORY_SEPARATOR . 'file2.php',
+ ],
+ ],
+
];
}