Repository: VKCOM/noverify
Pull request #872 (2021-01-11 10:55:44)

linter: fixed incorrect type resolution for a tuple containing static, self or $this


Patch:
diff --git a/src/linter/types.go b/src/linter/types.go
index 95a1379..733935d 100644
--- a/src/linter/types.go
+++ b/src/linter/types.go
@@ -147,6 +147,32 @@ func (conv *phpdocTypeConverter) mapShapeType(params []phpdoc.TypeExpr) []meta.T
 			continue
 		}
 		types := conv.mapType(typeExpr)
+
+		// We need to resolve the class names as well as static,
+		// self and $this here for it to work properly.
+		for i, typ := range types {
+			if _, ok := typeAliases[typ.Elem]; ok {
+				continue
+			}
+
+			if trivialTypes[typ.Elem] {
+				continue
+			}
+
+			className, ok := solver.GetClassName(conv.ctx.st, &ir.Name{Value: typ.Elem})
+			if !ok {
+				continue
+			}
+
+			types[i].Elem = className
+		}
+		if conv.nullable {
+			types = append(types, meta.Type{
+				Elem: "null",
+				Dims: 0,
+			})
+		}
+
 		props = append(props, shapeTypeProp{
 			key:   key.Value,
 			types: types,


Test patch:
diff --git a/src/tests/exprtype/exprtype_test.go b/src/tests/exprtype/exprtype_test.go
index a56bd12..16a4d59 100644
--- a/src/tests/exprtype/exprtype_test.go
+++ b/src/tests/exprtype/exprtype_test.go
@@ -2825,6 +2825,112 @@ exprtype(f8(), "\Foo");
 	runExprTypeTest(t, &exprTypeTestParams{code: code})
 }
 
+func TestNullableInTupleExprType(t *testing.T) {
+	code := `<?php
+class Boo {}
+
+class Foo {
+	/**
+	 * @return tuple(?Foo, int)
+	 */
+	public static function staticMethodNullable() {}
+
+	/**
+	 * @return tuple(?Boo, int)
+	 */
+	public static function staticMethodNullableBoo() {}
+
+	/**
+	 * @return tuple(?self, int)
+	 */
+	public static function staticMethodSelfNullable() {}
+}
+
+function f1() {
+	list($a, $_) = Foo::staticMethodNullable();
+	list($b, $_) = Foo::staticMethodNullableBoo();
+	list($c, $_) = Foo::staticMethodSelfNullable();
+	exprtype($a, "\Foo|null");
+	exprtype($b, "\Boo|null");
+	exprtype($c, "\Foo|null");
+}
+`
+	runExprTypeTest(t, &exprTypeTestParams{code: code})
+}
+
+func TestSelfStaticInTupleExprType(t *testing.T) {
+	code := `<?php
+class Foo {
+	/**
+	 * @return tuple(static, int)
+	 */
+	public static function staticMethodStatic() {}
+	
+	/**
+	 * @return tuple($this, int)
+	 */
+	public static function staticMethodThis() {}
+	
+	/**
+	 * @return tuple(self, int)
+	 */
+	public static function staticMethodSelf() {}
+}
+
+function f1() {
+    list($a, $_) = Foo::staticMethodStatic();
+    list($b, $_) = Foo::staticMethodThis();
+    list($c, $_) = Foo::staticMethodSelf();
+    exprtype($a, "\Foo");
+    exprtype($b, "\Foo");
+    exprtype($c, "\Foo");
+}
+`
+	runExprTypeTest(t, &exprTypeTestParams{code: code})
+}
+
+func TestClassesInTupleExprType(t *testing.T) {
+	code := `<?php
+namespace Boo {
+	class B {}
+	class C {}
+}
+
+namespace Foo {
+	use Boo\B;
+	use Boo\C as ClassFromBoo;
+
+	class F extends ClassFromBoo {
+		/**
+		 * @return tuple(parent, int)
+		 */
+		public static function method() {}
+	}
+	
+	/**
+	 * @return tuple(?B, integer)
+	 */
+	function f() {}
+
+	/**
+	 * @return tuple(ClassFromBoo, int)
+	 */
+	function f1() {}
+	
+	function f2() {
+		list($a, $_) = f();
+		list($b, $_) = f1();
+		list($c, $_) = F::method();
+		
+		exprtype($a, "\Boo\B|null");
+		exprtype($b, "\Boo\C");
+		exprtype($c, "\Boo\C");
+	}
+}
+`
+	runExprTypeTest(t, &exprTypeTestParams{code: code})
+}
+
 func runExprTypeTest(t *testing.T, params *exprTypeTestParams) {
 	meta.ResetInfo()
 	if params.stubs != "" {