@@ -188,6 +188,9 @@ constexpr Method mHasMethod{"hasMethod", PT_OP_HAS_METHOD};
188188constexpr Method mGetMethod {" getMethod" , PT_OP_GET_METHOD };
189189constexpr Method mIsStatic {" isStatic" };
190190constexpr Method mGetStaticObjectType {" getStaticObjectType" };
191+ constexpr Method mChangeBaseClass {" changeBaseClass" , PT_OP_CHANGE_BASE_CLASS };
192+ constexpr Method mGetClassReflection {" getClassReflection" , PT_OP_GET_CLASS_REFLECTION };
193+ constexpr Method mGetObjectClassReflections {" getObjectClassReflections" , PT_OP_GET_OBJECT_CLASS_REFLECTIONS };
191194constexpr Method mGetAncestorWithClassName {" getAncestorWithClassName" , PT_OP_GET_ANCESTOR_WITH_CLASS_NAME };
192195constexpr Method mGetClassName {" getClassName" };
193196constexpr Method mGetClassStringObjectType {" getClassStringObjectType" };
@@ -4679,6 +4682,15 @@ class InitializerExprTypeResolver
46794682 }
46804683 }
46814684
4685+ /* $fetchedOnType = count($referencedClasses) === 1 ? $constantClassType : new ObjectType($referencedClass) */
4686+ zv::Val fetchedOnType;
4687+ if (zend_hash_num_elements (Z_ARRVAL_P (referencedClasses.raw ())) == 1 ) {
4688+ fetchedOnType = zv::Val::copyOf (zv::Ref (constantClassType.raw ()));
4689+ } else {
4690+ fetchedOnType = objectTypeOf (referencedClass);
4691+ if (UNEXPECTED (fetchedOnType.isUndef ())) return zv::Val ();
4692+ }
4693+
46824694 IETR_VAL (constantClassReflection, reflectionProviderGetClass (referencedClass));
46834695 {
46844696 IETR_VAL (hasConstant, callOn (constantClassReflection.raw (), mHasConstant , 1 , constantName));
@@ -4758,7 +4770,8 @@ class InitializerExprTypeResolver
47584770 }
47594771 IETR_VAL (className, classReflectionName (constantClassReflection.raw ()));
47604772 IETR_VAL (phpDocType, callOn (constantClassReflection.raw (), mGetConstantPhpDocType , 1 , constantName));
4761- IETR_VAL (resolvedType, resolveClassConstantType (prop (slots::constantResolver), className.raw (), constantName, constantType.raw (), nativeType.raw (), phpDocType.raw ()));
4773+ IETR_VAL (resolvedClassConstantType, resolveClassConstantType (prop (slots::constantResolver), className.raw (), constantName, constantType.raw (), nativeType.raw (), phpDocType.raw ()));
4774+ IETR_VAL (resolvedType, transformStaticType (resolvedClassConstantType.raw (), fetchedOnType.raw ()));
47624775 writeArrayProperty (slots::classConstantValueTypeCache, resolvingName, resolvedType.raw ());
47634776 types.push (zv::Ref (resolvedType.raw ()));
47644777 unsetArrayProperty (slots::currentlyResolvingClassConstant, resolvingName);
@@ -4802,7 +4815,8 @@ class InitializerExprTypeResolver
48024815 IETR_VAL (nativeType, callOn (constantReflection.raw (), mGetNativeType ));
48034816 IETR_VAL (className, classReflectionName (constantClassReflection.raw ()));
48044817 IETR_VAL (phpDocType, callOn (constantClassReflection.raw (), mGetConstantPhpDocType , 1 , constantName));
4805- IETR_VAL (resolvedConstantType, resolveClassConstantType (prop (slots::constantResolver), className.raw (), constantName, constantType.raw (), nativeType.raw (), phpDocType.raw ()));
4818+ IETR_VAL (resolvedClassConstantType, resolveClassConstantType (prop (slots::constantResolver), className.raw (), constantName, constantType.raw (), nativeType.raw (), phpDocType.raw ()));
4819+ IETR_VAL (resolvedConstantType, transformStaticType (resolvedClassConstantType.raw (), fetchedOnType.raw ()));
48064820 unsetArrayProperty (slots::currentlyResolvingClassConstant, resolvingName);
48074821 types.push (std::move (resolvedConstantType));
48084822 }
@@ -4819,7 +4833,66 @@ class InitializerExprTypeResolver
48194833 }
48204834
48214835 IETR_VAL (constant, callOn (constantClassType.raw (), mGetConstant , 1 , constantName));
4822- return callOn (constant.raw (), mGetValueType );
4836+ IETR_VAL (valueType, callOn (constant.raw (), mGetValueType ));
4837+ return transformStaticType (valueType.raw (), constantClassType.raw ());
4838+ }
4839+
4840+ /* Mirrors transformStaticType(): TypeTraverser::map($type, the closure
4841+ * below over $fetchedOnType) */
4842+ static zv::Val transformStaticType (zval *type, zval *fetchedOnType)
4843+ {
4844+ IETR_VAL (callback, pt_type_native_callback (transformStaticTypeCallback, fetchedOnType, NULL ));
4845+ return pt_type_traverser_map_of (type, callback.raw ());
4846+ }
4847+
4848+ /* the closure of transformStaticType(): `static function (Type $type,
4849+ * callable $traverse) use ($fetchedOnType): Type` — a StaticType has its
4850+ * base class changed to the class of a StaticType fetched-on type, or,
4851+ * a GenericStaticType, becomes the static object type of its base class
4852+ * changed to the single fetched-on class; any other StaticType becomes
4853+ * the fetched-on type; everything else traverses */
4854+ static void transformStaticTypeCallback (zval *fetchedOnType, zval *state1, uint32_t argc, zval *argv, zval *return_value)
4855+ {
4856+ (void ) state1;
4857+ if (UNEXPECTED (argc < 2 )) {
4858+ zend_argument_count_error (" Too few arguments to function %s::{closure}(), %u passed and exactly 2 expected" , ZSTR_VAL (pt_ce_initializer_expr_type_resolver->name ), argc);
4859+ return ;
4860+ }
4861+ zval *type = &argv[0 ];
4862+ zv::Val result = transformStaticTypeStep (type, fetchedOnType, &argv[1 ]);
4863+ if (UNEXPECTED (result.isUndef ())) return ;
4864+ result.intoReturnValue (return_value);
4865+ }
4866+
4867+ static zv::Val transformStaticTypeStep (zval *type, zval *fetchedOnType, zval *traverse)
4868+ {
4869+ if (!isA (type, pt_ce_static_type)) {
4870+ return pt_type_call_callable (traverse, 1 , type);
4871+ }
4872+ if (isA (fetchedOnType, pt_ce_static_type)) {
4873+ /* return $traverse($type->changeBaseClass($fetchedOnType->getClassReflection())); */
4874+ IETR_VAL (fetchedOnReflection, callOn (fetchedOnType, mGetClassReflection ));
4875+ IETR_VAL (changed, callOn (type, mChangeBaseClass , 1 , fetchedOnReflection.raw ()));
4876+ return pt_type_call_callable (traverse, 1 , changed.raw ());
4877+ }
4878+ if (isA (type, pt_ce_generic_static_type)) {
4879+ IETR_VAL (reflections, callOn (fetchedOnType, mGetObjectClassReflections ));
4880+ if (UNEXPECTED (!requireArray (reflections.raw ()))) return zv::Val ();
4881+ if (zend_hash_num_elements (Z_ARRVAL_P (reflections.raw ())) == 1 ) {
4882+ /* return $traverse($type->changeBaseClass($fetchedOnTypeReflections[0])->getStaticObjectType()); */
4883+ zval *first = zend_hash_index_find (Z_ARRVAL_P (reflections.raw ()), 0 );
4884+ if (UNEXPECTED (first == NULL )) {
4885+ zend_throw_error (NULL , " phpstan_turbo: getObjectClassReflections() must return a list" );
4886+ return zv::Val ();
4887+ }
4888+ ZVAL_DEREF (first);
4889+ IETR_VAL (changed, callOn (type, mChangeBaseClass , 1 , first));
4890+ IETR_VAL (staticObjectType, callOn (changed.raw (), mGetStaticObjectType ));
4891+ return pt_type_call_callable (traverse, 1 , staticObjectType.raw ());
4892+ }
4893+ }
4894+
4895+ return zv::Val::copyOf (zv::Ref (fetchedOnType));
48234896 }
48244897
48254898 /* Mirrors getClassConstFetchType(); $className PHP null for null */
@@ -5986,6 +6059,13 @@ PT_MINIT_REGISTRATION(pt_register_initializer_expr_type_resolver)
59866059 PT_RETURN_VAL (IETR_THIS .getReflectionProvider ());
59876060 });
59886061
6062+ cls.method (sigs::transformStaticType, [](INTERNAL_FUNCTION_PARAMETERS ) {
6063+ zval *type, *fetchedOnType;
6064+ if (!zp::parse<zp::Obj, zp::Obj>(execute_data, type, fetchedOnType)) RETURN_THROWS ();
6065+ if (UNEXPECTED (!ietrArgType (type, 1 ) || !ietrArgType (fetchedOnType, 2 ))) RETURN_THROWS ();
6066+ PT_RETURN_VAL (InitializerExprTypeResolver::transformStaticType (type, fetchedOnType));
6067+ });
6068+
59896069 cls.method (sigs::getNeverType, [](INTERNAL_FUNCTION_PARAMETERS ) {
59906070 zval *leftType, *rightType;
59916071 if (!zp::parse<zp::Obj, zp::Obj>(execute_data, leftType, rightType)) RETURN_THROWS ();
0 commit comments