diff --git a/src/Psalm/Internal/Type/SimpleAssertionReconciler.php b/src/Psalm/Internal/Type/SimpleAssertionReconciler.php index 66cfc2f9e96..c8bd3c1b21d 100644 --- a/src/Psalm/Internal/Type/SimpleAssertionReconciler.php +++ b/src/Psalm/Internal/Type/SimpleAssertionReconciler.php @@ -1435,7 +1435,7 @@ private static function reconcileScalar( if ($type instanceof Scalar) { $scalar_types[] = $type; } elseif ($type instanceof TTemplateParam) { - if ($type->as->hasScalar() || $type->as->hasMixed()) { + if ($type->as->hasScalarType() || $type->as->hasMixed()) { $type = $type->replaceAs(self::reconcileScalar( $assertion, $type->as, @@ -1526,7 +1526,7 @@ private static function reconcileNumeric( $numeric_types[] = new TInt(); $numeric_types[] = new TNumericString(); } elseif ($type instanceof TTemplateParam) { - if ($type->as->hasNumeric() || $type->as->hasMixed()) { + if ($type->as->hasScalarType() || $type->as->hasMixed()) { $type = $type->replaceAs(self::reconcileNumeric( $assertion, $type->as, @@ -1605,14 +1605,6 @@ private static function reconcileObject( /** @var TNamedObject|TTemplateParam|TIterable|TObjectWithProperties|TCallableObject $assertion_type */ $object_types[] = $type->addIntersectionType($assertion_type); $redundant = false; - } elseif ($type->isObjectType()) { - if ($assertion_type_is_intersectable_type - && !self::areIntersectionTypesAllowed($codebase, $type) - ) { - $redundant = false; - } else { - $object_types[] = $type; - } } elseif ($type instanceof TCallable) { $callable_object = new TCallableObject($type->from_docblock, $type); $object_types[] = $callable_object; @@ -1624,7 +1616,7 @@ private static function reconcileObject( $object_types[] = $type; $redundant = false; } elseif ($type instanceof TTemplateParam) { - if ($type->as->hasObject() || $type->as->hasMixed()) { + if ($type->as->hasObjectType() || $type->as->hasMixed()) { /** * @psalm-suppress PossiblyInvalidArgument This looks wrong, psalm assumes that $assertion_type * can contain TNamedObject due to the reconciliation above @@ -1650,6 +1642,14 @@ private static function reconcileObject( } $redundant = false; + } elseif ($type->isObjectType()) { + if ($assertion_type_is_intersectable_type + && !self::areIntersectionTypesAllowed($codebase, $type) + ) { + $redundant = false; + } else { + $object_types[] = $type; + } } elseif ($type instanceof TIterable) { $params = $type->type_params; $params[0] = self::refineArrayKey($params[0]); diff --git a/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php b/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php index 0308d741900..81e4208d7df 100644 --- a/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php +++ b/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php @@ -476,11 +476,29 @@ private static function reconcileBool( foreach ($existing_var_type->getAtomicTypes() as $type) { if ($type instanceof TTemplateParam) { - if (!$type->as->hasBool()) { + if (!$is_equality && !$type->as->isMixed()) { + $template_did_fail = 0; + + $type = $type->replaceAs(self::reconcileBool( + $assertion, + $type->as, + null, + false, + null, + $suppressed_issues, + $template_did_fail, + $is_equality, + )); + + $redundant = false; + + if (!$template_did_fail) { + $non_bool_types[] = $type; + } + } else { + $redundant = false; $non_bool_types[] = $type; } - - $redundant = false; } elseif (!$type instanceof TBool || ($is_equality && get_class($type) === TBool::class) ) { diff --git a/tests/Template/FunctionTemplateTest.php b/tests/Template/FunctionTemplateTest.php index 01b58efb671..4441f0e109c 100644 --- a/tests/Template/FunctionTemplateTest.php +++ b/tests/Template/FunctionTemplateTest.php @@ -808,6 +808,29 @@ function bar($foo): void { if (!is_callable($foo)) {} }', ], + 'assertOnUnionTemplatedValue' => [ + 'code' => ' [ 'code' => '