diff --git a/UPGRADE.md b/UPGRADE.md
index 987b27f01..0b5bc60cb 100644
--- a/UPGRADE.md
+++ b/UPGRADE.md
@@ -422,7 +422,7 @@ class MyType extends ScalarType {
 ### Breaking: Descriptions in comments are not used as descriptions by default anymore
 
 Descriptions now need to be inside Strings or BlockStrings in order to be picked up as
-description. If you want to keep the old behaviour you can supply the option `commentDescriptions`
+description. If you want to keep the old behavior you can supply the option `commentDescriptions`
 to BuildSchema::buildAST(), BuildSchema::build() or Printer::doPrint().
 
 Here is the official way now to define descriptions in the graphQL language:
diff --git a/composer.json b/composer.json
index ca2723523..4ec80a24e 100644
--- a/composer.json
+++ b/composer.json
@@ -23,7 +23,7 @@
     "nyholm/psr7": "^1.5",
     "phpbench/phpbench": "^1.2",
     "phpstan/extension-installer": "^1.1",
-    "phpstan/phpstan": "2.1.11",
+    "phpstan/phpstan": "2.1.15",
     "phpstan/phpstan-phpunit": "2.0.6",
     "phpstan/phpstan-strict-rules": "2.0.4",
     "phpunit/phpunit": "^9.5 || ^10.5.21 || ^11",
diff --git a/rector.php b/rector.php
index 851d258cd..93cfa0373 100644
--- a/rector.php
+++ b/rector.php
@@ -19,13 +19,13 @@
         Rector\CodeQuality\Rector\ClassMethod\LocallyCalledStaticMethodToNonStaticRector::class, // static methods are fine
         Rector\CodeQuality\Rector\Foreach_\UnusedForeachValueToArrayKeysRector::class, // Less efficient
         Rector\CodeQuality\Rector\Switch_\SwitchTrueToIfRector::class, // More expressive in some cases
-        Rector\DeadCode\Rector\If_\RemoveAlwaysTrueIfConditionRector::class, // Sometimes necessary to prove runtime behaviour matches defined types
-        Rector\DeadCode\Rector\If_\RemoveDeadInstanceOfRector::class, // Sometimes necessary to prove runtime behaviour matches defined types
+        Rector\DeadCode\Rector\If_\RemoveAlwaysTrueIfConditionRector::class, // Sometimes necessary to prove runtime behavior matches defined types
+        Rector\DeadCode\Rector\If_\RemoveDeadInstanceOfRector::class, // Sometimes necessary to prove runtime behavior matches defined types
         Rector\DeadCode\Rector\Node\RemoveNonExistingVarAnnotationRector::class, // Sometimes false-positive
         Rector\DeadCode\Rector\Property\RemoveUnusedPrivatePropertyRector::class, // TODO reintroduce when https://github.com/rectorphp/rector-src/pull/4491 is released
         Rector\PHPUnit\CodeQuality\Rector\Class_\NarrowUnusedSetUpDefinedPropertyRector::class, // Sometimes nicer for symmetry
         Rector\PHPUnit\CodeQuality\Rector\Class_\PreferPHPUnitThisCallRector::class, // Prefer self::
-        Rector\PHPUnit\CodeQuality\Rector\MethodCall\AssertCountWithZeroToAssertEmptyRector::class, // imprecise
+        Rector\PHPUnit\CodeQuality\Rector\Class_\RemoveDataProviderParamKeysRector::class, // Less clear
         Rector\PHPUnit\CodeQuality\Rector\MethodCall\AssertPropertyExistsRector::class, // Uses deprecated PHPUnit methods
         Rector\PHPUnit\CodeQuality\Rector\MethodCall\AssertIssetToSpecificMethodRector::class => [
             __DIR__ . '/tests/Utils/MixedStoreTest.php', // Uses keys that are not string or int
diff --git a/src/Type/Definition/CustomScalarType.php b/src/Type/Definition/CustomScalarType.php
index 64cd887de..80694dca9 100644
--- a/src/Type/Definition/CustomScalarType.php
+++ b/src/Type/Definition/CustomScalarType.php
@@ -101,19 +101,19 @@ public function assertValid(): void
             throw new InvariantViolation("{$this->name} must provide \"parseValue\" and \"parseLiteral\" functions, \"serialize\" function, or both.");
         }
 
-        // @phpstan-ignore-next-line not necessary according to types, but can happen during runtime
+        // @phpstan-ignore-next-line unnecessary according to types, but can happen during runtime
         if ($hasSerialize && ! is_callable($serialize)) {
             $notCallable = Utils::printSafe($serialize);
             throw new InvariantViolation("{$this->name} must provide \"serialize\" as a callable if given, but got: {$notCallable}.");
         }
 
-        // @phpstan-ignore-next-line not necessary according to types, but can happen during runtime
+        // @phpstan-ignore-next-line unnecessary according to types, but can happen during runtime
         if ($hasParseValue && ! is_callable($parseValue)) {
             $notCallable = Utils::printSafe($parseValue);
             throw new InvariantViolation("{$this->name} must provide \"parseValue\" as a callable if given, but got: {$notCallable}.");
         }
 
-        // @phpstan-ignore-next-line not necessary according to types, but can happen during runtime
+        // @phpstan-ignore-next-line unnecessary according to types, but can happen during runtime
         if ($hasParseLiteral && ! is_callable($parseLiteral)) {
             $notCallable = Utils::printSafe($parseLiteral);
             throw new InvariantViolation("{$this->name} must provide \"parseLiteral\" as a callable if given, but got: {$notCallable}.");
diff --git a/src/Type/Definition/EnumType.php b/src/Type/Definition/EnumType.php
index e30fc7b2b..4182a3545 100644
--- a/src/Type/Definition/EnumType.php
+++ b/src/Type/Definition/EnumType.php
@@ -222,7 +222,7 @@ public function assertValid(): void
     {
         Utils::assertValidName($this->name);
 
-        $values = $this->config['values'] ?? null;
+        $values = $this->config['values'] ?? null; // @phpstan-ignore nullCoalesce.initializedProperty (unnecessary according to types, but can happen during runtime)
         if (! is_iterable($values) && ! is_callable($values)) {
             $notIterable = Utils::printSafe($values);
             throw new InvariantViolation("{$this->name} values must be an iterable or callable, got: {$notIterable}");
diff --git a/src/Type/Definition/FieldDefinition.php b/src/Type/Definition/FieldDefinition.php
index 02f6ddafb..788dab9f2 100644
--- a/src/Type/Definition/FieldDefinition.php
+++ b/src/Type/Definition/FieldDefinition.php
@@ -238,7 +238,7 @@ public function assertValid(Type $parentType): void
             throw new InvariantViolation("{$parentType->name}.{$this->name} field type must be Output Type but got: {$safeType}.");
         }
 
-        // @phpstan-ignore-next-line not necessary according to types, but can happen during runtime
+        // @phpstan-ignore-next-line unnecessary according to types, but can happen during runtime
         if ($this->resolveFn !== null && ! is_callable($this->resolveFn)) {
             $safeResolveFn = Utils::printSafe($this->resolveFn);
             throw new InvariantViolation("{$parentType->name}.{$this->name} field resolver must be a function if provided, but got: {$safeResolveFn}.");
diff --git a/src/Type/Definition/InputObjectType.php b/src/Type/Definition/InputObjectType.php
index 3cf82699d..3e9c1ad9e 100644
--- a/src/Type/Definition/InputObjectType.php
+++ b/src/Type/Definition/InputObjectType.php
@@ -181,7 +181,7 @@ public function assertValid(): void
     {
         Utils::assertValidName($this->name);
 
-        $fields = $this->config['fields'] ?? null;
+        $fields = $this->config['fields'] ?? null; // @phpstan-ignore nullCoalesce.initializedProperty (unnecessary according to types, but can happen during runtime)
         if (is_callable($fields)) {
             $fields = $fields();
         }
diff --git a/src/Type/Definition/InterfaceType.php b/src/Type/Definition/InterfaceType.php
index d4e42e9b0..3506e9b35 100644
--- a/src/Type/Definition/InterfaceType.php
+++ b/src/Type/Definition/InterfaceType.php
@@ -85,7 +85,7 @@ public function assertValid(): void
         Utils::assertValidName($this->name);
 
         $resolveType = $this->config['resolveType'] ?? null;
-        // @phpstan-ignore-next-line not necessary according to types, but can happen during runtime
+        // @phpstan-ignore-next-line unnecessary according to types, but can happen during runtime
         if ($resolveType !== null && ! is_callable($resolveType)) {
             $notCallable = Utils::printSafe($resolveType);
             throw new InvariantViolation("{$this->name} must provide \"resolveType\" as null or a callable, but got: {$notCallable}.");
diff --git a/src/Type/Definition/NamedTypeImplementation.php b/src/Type/Definition/NamedTypeImplementation.php
index cb5f65811..f47f9e4d4 100644
--- a/src/Type/Definition/NamedTypeImplementation.php
+++ b/src/Type/Definition/NamedTypeImplementation.php
@@ -4,9 +4,7 @@
 
 use GraphQL\Error\InvariantViolation;
 
-/**
- * @see NamedType
- */
+/** @see NamedType */
 trait NamedTypeImplementation
 {
     public string $name;
@@ -21,7 +19,7 @@ public function toString(): string
     /** @throws InvariantViolation */
     protected function inferName(): string
     {
-        if (isset($this->name)) {
+        if (isset($this->name)) { // @phpstan-ignore-line property might be uninitialized
             return $this->name;
         }
 
diff --git a/src/Type/Definition/ObjectType.php b/src/Type/Definition/ObjectType.php
index 63b7c5d99..cb34bbc56 100644
--- a/src/Type/Definition/ObjectType.php
+++ b/src/Type/Definition/ObjectType.php
@@ -154,7 +154,7 @@ public function assertValid(): void
         Utils::assertValidName($this->name);
 
         $isTypeOf = $this->config['isTypeOf'] ?? null;
-        // @phpstan-ignore-next-line not necessary according to types, but can happen during runtime
+        // @phpstan-ignore-next-line unnecessary according to types, but can happen during runtime
         if (isset($isTypeOf) && ! is_callable($isTypeOf)) {
             $notCallable = Utils::printSafe($isTypeOf);
             throw new InvariantViolation("{$this->name} must provide \"isTypeOf\" as null or a callable, but got: {$notCallable}.");
diff --git a/src/Type/Definition/UnionType.php b/src/Type/Definition/UnionType.php
index 3b6535bfa..935ac60eb 100644
--- a/src/Type/Definition/UnionType.php
+++ b/src/Type/Definition/UnionType.php
@@ -89,7 +89,7 @@ public function getTypes(): array
         if (! isset($this->types)) {
             $this->types = [];
 
-            $types = $this->config['types'] ?? null;
+            $types = $this->config['types'] ?? null; // @phpstan-ignore nullCoalesce.initializedProperty (unnecessary according to types, but can happen during runtime)
             if (is_callable($types)) {
                 $types = $types();
             }
@@ -120,7 +120,7 @@ public function assertValid(): void
         Utils::assertValidName($this->name);
 
         $resolveType = $this->config['resolveType'] ?? null;
-        // @phpstan-ignore-next-line not necessary according to types, but can happen during runtime
+        // @phpstan-ignore-next-line unnecessary according to types, but can happen during runtime
         if (isset($resolveType) && ! is_callable($resolveType)) {
             $notCallable = Utils::printSafe($resolveType);
             throw new InvariantViolation("{$this->name} must provide \"resolveType\" as null or a callable, but got: {$notCallable}.");
diff --git a/src/Utils/Utils.php b/src/Utils/Utils.php
index c171a3fcb..ee122b115 100644
--- a/src/Utils/Utils.php
+++ b/src/Utils/Utils.php
@@ -116,7 +116,10 @@ public static function chr(int $ord, string $encoding = 'UTF-8'): string
             return pack('N', $ord);
         }
 
-        return mb_convert_encoding(self::chr($ord, 'UCS-4BE'), $encoding, 'UCS-4BE');
+        $converted = mb_convert_encoding(self::chr($ord, 'UCS-4BE'), $encoding, 'UCS-4BE');
+        assert(is_string($converted), 'format string is statically known to be correct');
+
+        return $converted;
     }
 
     /** UTF-8 compatible ord(). */
@@ -128,10 +131,13 @@ public static function ord(string $char, string $encoding = 'UTF-8'): int
 
         if ($encoding !== 'UCS-4BE') {
             $char = mb_convert_encoding($char, 'UCS-4BE', $encoding);
+            assert(is_string($char), 'format string is statically known to be correct');
         }
 
-        // @phpstan-ignore-next-line format string is statically known to be correct
-        return unpack('N', $char)[1];
+        $unpacked = unpack('N', $char);
+        assert(is_array($unpacked), 'format string is statically known to be correct');
+
+        return $unpacked[1];
     }
 
     /** Returns UTF-8 char code at given $positing of the $string. */
diff --git a/tests/Language/PrinterTest.php b/tests/Language/PrinterTest.php
index 480e24322..526eecb27 100644
--- a/tests/Language/PrinterTest.php
+++ b/tests/Language/PrinterTest.php
@@ -44,7 +44,7 @@ public function testPrintsMinimalAst(): void
     /** @see it('produces helpful error messages', () => { */
     public function testProducesHelpfulErrorMessages(): void
     {
-        self::markTestSkipped('Not necessary because our class based AST makes it impossible to pass bad data.');
+        self::markTestSkipped('Unnecessary because our class based AST makes it impossible to pass bad data.');
     }
 
     /** @see it('correctly prints non-query operations without name', () => { */
diff --git a/tests/TestCaseBase.php b/tests/TestCaseBase.php
index e4b0ab162..5da54f382 100644
--- a/tests/TestCaseBase.php
+++ b/tests/TestCaseBase.php
@@ -9,7 +9,7 @@
 abstract class TestCaseBase extends TestCase
 {
     /**
-     * Useful to test code with no observable behaviour other than not crashing.
+     * Useful to test code with no observable behavior other than not crashing.
      *
      * In contrast to PHPUnit's native method, this lets the test case count towards coverage.
      *
diff --git a/tests/Utils/BuildSchemaTest.php b/tests/Utils/BuildSchemaTest.php
index 9e068e3c7..b222f88b5 100644
--- a/tests/Utils/BuildSchemaTest.php
+++ b/tests/Utils/BuildSchemaTest.php
@@ -1212,7 +1212,7 @@ public function testCanBuildInvalidSchema(): void
     /** @see it('Do not override standard types') */
     public function testDoNotOverrideStandardTypes(): void
     {
-        // NOTE: not sure it's desired behaviour to just silently ignore override
+        // NOTE: not sure it's desired behavior to just silently ignore override
         // attempts so just documenting it here.
 
         $schema = BuildSchema::build('
diff --git a/tests/Utils/CoerceInputValueTest.php b/tests/Utils/CoerceInputValueTest.php
index 082ce59ef..90ba5e06c 100644
--- a/tests/Utils/CoerceInputValueTest.php
+++ b/tests/Utils/CoerceInputValueTest.php
@@ -479,7 +479,7 @@ public function testReturnsNestedNullForNestedNullValues(): void
      * @see it('throw error without path', () => {
      * @see it('throw error with path', () => {
      *
-     * Not necessary because we do not implement the callback variant coerceInputValue.
+     * Unnecessary because we do not implement the callback variant coerceInputValue.
      */
 
     /**