diff --git a/composer.json b/composer.json
index f132544d..44e9701c 100644
--- a/composer.json
+++ b/composer.json
@@ -36,7 +36,8 @@
     "symfony/messenger": "^5.4",
     "symfony/polyfill-php80": "^1.24",
     "symfony/serializer": "^5.4",
-    "symfony/service-contracts": "^2.2.0"
+    "symfony/service-contracts": "^2.2.0",
+    "twig/twig": "^3.0"
   },
   "config": {
     "sort-packages": true
diff --git a/extension.neon b/extension.neon
index 512f9908..567ec50d 100644
--- a/extension.neon
+++ b/extension.neon
@@ -11,6 +11,7 @@ parameters:
 		constantHassers: true
 		console_application_loader: null
 		consoleApplicationLoader: null
+		twigEnvironmentLoader: null
 	featureToggles:
 		skipCheckGenericClasses:
 			- Symfony\Component\Form\AbstractType
@@ -115,6 +116,7 @@ parametersSchema:
 		constantHassers: bool()
 		console_application_loader: schema(string(), nullable())
 		consoleApplicationLoader: schema(string(), nullable())
+		twigEnvironmentLoader: schema(string(), nullable())
 	])
 
 services:
@@ -365,3 +367,8 @@ services:
 	-
 		factory: PHPStan\Type\Symfony\ExtensionGetConfigurationReturnTypeExtension
 		tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
+
+	-
+		class: PHPStan\Symfony\TwigEnvironmentResolver
+		arguments:
+			twigEnvironmentLoader: %symfony.twigEnvironmentLoader%
diff --git a/rules.neon b/rules.neon
index cedcea7a..baaa6e1f 100644
--- a/rules.neon
+++ b/rules.neon
@@ -5,4 +5,5 @@ rules:
 	- PHPStan\Rules\Symfony\InvalidArgumentDefaultValueRule
 	- PHPStan\Rules\Symfony\UndefinedOptionRule
 	- PHPStan\Rules\Symfony\InvalidOptionDefaultValueRule
+	- PHPStan\Rules\Symfony\TwigTemplateExistsRule
 
diff --git a/src/Rules/Symfony/TwigTemplateExistsRule.php b/src/Rules/Symfony/TwigTemplateExistsRule.php
new file mode 100644
index 00000000..e40cc184
--- /dev/null
+++ b/src/Rules/Symfony/TwigTemplateExistsRule.php
@@ -0,0 +1,104 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Rules\Symfony;
+
+use PhpParser\Node;
+use PhpParser\Node\Arg;
+use PhpParser\Node\Expr\MethodCall;
+use PhpParser\Node\Expr\Variable;
+use PhpParser\Node\Identifier;
+use PhpParser\Node\Scalar\String_;
+use PHPStan\Analyser\Scope;
+use PHPStan\Rules\Rule;
+use PHPStan\Rules\RuleErrorBuilder;
+use PHPStan\Symfony\TwigEnvironmentResolver;
+use PHPStan\Type\ObjectType;
+use function count;
+use function in_array;
+use function is_string;
+use function sprintf;
+
+/**
+ * @implements Rule<MethodCall>
+ */
+final class TwigTemplateExistsRule implements Rule
+{
+
+	/** @var TwigEnvironmentResolver */
+	private $twigEnvironmentResolver;
+
+	public function __construct(TwigEnvironmentResolver $twigEnvironmentResolver)
+	{
+		$this->twigEnvironmentResolver = $twigEnvironmentResolver;
+	}
+
+	public function getNodeType(): string
+	{
+		return MethodCall::class;
+	}
+
+	public function processNode(Node $node, Scope $scope): array
+	{
+		$templateArg = $this->getTwigTemplateArg($node, $scope);
+
+		if ($templateArg === null) {
+			return [];
+		}
+
+		$templateNames = [];
+
+		if ($templateArg->value instanceof Variable && is_string($templateArg->value->name)) {
+			$varType = $scope->getVariableType($templateArg->value->name);
+
+			foreach ($varType->getConstantStrings() as $constantString) {
+				$templateNames[] = $constantString->getValue();
+			}
+		} elseif ($templateArg->value instanceof String_) {
+			$templateNames[] = $templateArg->value->value;
+		}
+
+		if (count($templateNames) === 0) {
+			return [];
+		}
+
+		$errors = [];
+
+		foreach ($templateNames as $templateName) {
+			if ($this->twigEnvironmentResolver->templateExists($templateName)) {
+				continue;
+			}
+
+			$errors[] = RuleErrorBuilder::message(sprintf(
+				'Twig template "%s" does not exist.',
+				$templateName
+			))->line($templateArg->getStartLine())->identifier('twig.templateNotFound')->build();
+		}
+
+		return $errors;
+	}
+
+	private function getTwigTemplateArg(MethodCall $node, Scope $scope): ?Arg
+	{
+		if (!$node->name instanceof Identifier) {
+			return null;
+		}
+
+		$argType = $scope->getType($node->var);
+		$methodName = $node->name->name;
+
+		if ((new ObjectType('Symfony\Bundle\FrameworkBundle\Controller\AbstractController'))->isSuperTypeOf($argType)->yes() && in_array($methodName, ['render', 'renderView', 'renderBlockView', 'renderBlock', 'renderForm', 'stream'], true)) {
+			return $node->getArgs()[0] ?? null;
+		}
+
+		if ((new ObjectType('Twig\Environment'))->isSuperTypeOf($argType)->yes() && in_array($methodName, ['render', 'display', 'load'], true)) {
+			return $node->getArgs()[0] ?? null;
+		}
+
+		if ((new ObjectType('Symfony\Bridge\Twig\Mime\TemplatedEmail'))->isSuperTypeOf($argType)->yes() && in_array($methodName, ['htmlTemplate', 'textTemplate'], true)) {
+			return $node->getArgs()[0] ?? null;
+		}
+
+		return null;
+	}
+
+}
diff --git a/src/Symfony/TwigEnvironmentResolver.php b/src/Symfony/TwigEnvironmentResolver.php
new file mode 100644
index 00000000..9ffe0245
--- /dev/null
+++ b/src/Symfony/TwigEnvironmentResolver.php
@@ -0,0 +1,55 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Symfony;
+
+use PHPStan\ShouldNotHappenException;
+use Twig\Environment;
+use function file_exists;
+use function is_readable;
+use function sprintf;
+
+final class TwigEnvironmentResolver
+{
+
+	/** @var string|null */
+	private $twigEnvironmentLoader;
+
+	/** @var Environment|null */
+	private $twigEnvironment;
+
+	public function __construct(?string $twigEnvironmentLoader)
+	{
+		$this->twigEnvironmentLoader = $twigEnvironmentLoader;
+	}
+
+	private function getTwigEnvironment(): ?Environment
+	{
+		if ($this->twigEnvironmentLoader === null) {
+			return null;
+		}
+
+		if ($this->twigEnvironment !== null) {
+			return $this->twigEnvironment;
+		}
+
+		if (!file_exists($this->twigEnvironmentLoader)
+			|| !is_readable($this->twigEnvironmentLoader)
+		) {
+			throw new ShouldNotHappenException(sprintf('Cannot load Twig environment. Check the parameters.symfony.twigEnvironmentLoader setting in PHPStan\'s config. The offending value is "%s".', $this->twigEnvironmentLoader));
+		}
+
+		return $this->twigEnvironment = require $this->twigEnvironmentLoader;
+	}
+
+	public function templateExists(string $name): bool
+	{
+		$twigEnvironment = $this->getTwigEnvironment();
+
+		if ($twigEnvironment === null) {
+			return true;
+		}
+
+		return $twigEnvironment->getLoader()->exists($name);
+	}
+
+}
diff --git a/tests/Rules/Symfony/ExampleTwigController.php b/tests/Rules/Symfony/ExampleTwigController.php
new file mode 100644
index 00000000..c434d180
--- /dev/null
+++ b/tests/Rules/Symfony/ExampleTwigController.php
@@ -0,0 +1,71 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Rules\Symfony;
+
+use Symfony\Bridge\Twig\Mime\TemplatedEmail;
+use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Twig\Environment;
+use function rand;
+
+final class ExampleTwigController extends AbstractController
+{
+
+	public function foo(): void
+	{
+		$this->render('foo.html.twig');
+		$this->renderBlock('foo.html.twig');
+		$this->renderBlockView('foo.html.twig');
+		$this->renderForm('foo.html.twig');
+		$this->renderView('foo.html.twig');
+		$this->stream('foo.html.twig');
+
+		$this->render('bar.html.twig');
+		$this->renderBlock('bar.html.twig');
+		$this->renderBlockView('bar.html.twig');
+		$this->renderForm('bar.html.twig');
+		$this->renderView('bar.html.twig');
+		$this->stream('bar.html.twig');
+
+		$twig = new Environment();
+
+		$twig->render('foo.html.twig');
+		$twig->display('foo.html.twig');
+		$twig->load('foo.html.twig');
+
+		$twig->render('bar.html.twig');
+		$twig->display('bar.html.twig');
+		$twig->load('bar.html.twig');
+
+		$templatedEmail = new TemplatedEmail();
+
+		$templatedEmail->htmlTemplate('foo.html.twig');
+		$templatedEmail->textTemplate('foo.html.twig');
+
+		$templatedEmail->textTemplate('bar.html.twig');
+		$templatedEmail->textTemplate('bar.html.twig');
+
+		$name = 'foo.html.twig';
+
+		$this->render($name);
+
+		$name = 'bar.html.twig';
+
+		$this->render($name);
+
+		$name = rand(0, 1) ? 'foo.html.twig' : 'bar.html.twig';
+
+		$this->render($name);
+
+		$name = rand(0, 1) ? 'bar.html.twig' : 'baz.html.twig';
+
+		$this->render($name);
+
+		$this->render($this->getName());
+	}
+
+	private function getName(): string
+	{
+		return 'baz.html.twig';
+	}
+
+}
diff --git a/tests/Rules/Symfony/TwigTemplateExistsRuleNoTemplatesTest.php b/tests/Rules/Symfony/TwigTemplateExistsRuleNoTemplatesTest.php
new file mode 100644
index 00000000..7c65fe8b
--- /dev/null
+++ b/tests/Rules/Symfony/TwigTemplateExistsRuleNoTemplatesTest.php
@@ -0,0 +1,30 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Rules\Symfony;
+
+use PHPStan\Rules\Rule;
+use PHPStan\Symfony\TwigEnvironmentResolver;
+use PHPStan\Testing\RuleTestCase;
+
+/**
+ * @extends RuleTestCase<TwigTemplateExistsRule>
+ */
+final class TwigTemplateExistsRuleNoTemplatesTest extends RuleTestCase
+{
+
+	protected function getRule(): Rule
+	{
+		return new TwigTemplateExistsRule(new TwigEnvironmentResolver(null));
+	}
+
+	public function testGetArgument(): void
+	{
+		$this->analyse(
+			[
+				__DIR__ . '/ExampleTwigController.php',
+			],
+			[]
+		);
+	}
+
+}
diff --git a/tests/Rules/Symfony/TwigTemplateExistsRuleTest.php b/tests/Rules/Symfony/TwigTemplateExistsRuleTest.php
new file mode 100644
index 00000000..c3f96195
--- /dev/null
+++ b/tests/Rules/Symfony/TwigTemplateExistsRuleTest.php
@@ -0,0 +1,91 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Rules\Symfony;
+
+use PHPStan\Rules\Rule;
+use PHPStan\Symfony\TwigEnvironmentResolver;
+use PHPStan\Testing\RuleTestCase;
+
+/**
+ * @extends RuleTestCase<TwigTemplateExistsRule>
+ */
+final class TwigTemplateExistsRuleTest extends RuleTestCase
+{
+
+	protected function getRule(): Rule
+	{
+		return new TwigTemplateExistsRule(new TwigEnvironmentResolver(__DIR__ . '/twig_environment_loader.php'));
+	}
+
+	public function testGetArgument(): void
+	{
+		$this->analyse(
+			[
+				__DIR__ . '/ExampleTwigController.php',
+			],
+			[
+				[
+					'Twig template "bar.html.twig" does not exist.',
+					22,
+				],
+				[
+					'Twig template "bar.html.twig" does not exist.',
+					23,
+				],
+				[
+					'Twig template "bar.html.twig" does not exist.',
+					24,
+				],
+				[
+					'Twig template "bar.html.twig" does not exist.',
+					25,
+				],
+				[
+					'Twig template "bar.html.twig" does not exist.',
+					26,
+				],
+				[
+					'Twig template "bar.html.twig" does not exist.',
+					27,
+				],
+				[
+					'Twig template "bar.html.twig" does not exist.',
+					35,
+				],
+				[
+					'Twig template "bar.html.twig" does not exist.',
+					36,
+				],
+				[
+					'Twig template "bar.html.twig" does not exist.',
+					37,
+				],
+				[
+					'Twig template "bar.html.twig" does not exist.',
+					44,
+				],
+				[
+					'Twig template "bar.html.twig" does not exist.',
+					45,
+				],
+				[
+					'Twig template "bar.html.twig" does not exist.',
+					53,
+				],
+				[
+					'Twig template "bar.html.twig" does not exist.',
+					57,
+				],
+				[
+					'Twig template "bar.html.twig" does not exist.',
+					61,
+				],
+				[
+					'Twig template "baz.html.twig" does not exist.',
+					61,
+				],
+			]
+		);
+	}
+
+}
diff --git a/tests/Rules/Symfony/twig_environment_loader.php b/tests/Rules/Symfony/twig_environment_loader.php
new file mode 100644
index 00000000..822a8e8a
--- /dev/null
+++ b/tests/Rules/Symfony/twig_environment_loader.php
@@ -0,0 +1,10 @@
+<?php declare(strict_types = 1);
+
+use Twig\Environment;
+use Twig\Loader\ArrayLoader;
+
+require_once __DIR__ . '/../../../vendor/autoload.php';
+
+$loader = new ArrayLoader(['foo.html.twig' => 'foo']);
+
+return new Environment($loader);