Repository: VKCOM/noverify
Pull request #1148 (2021-09-06 10:27:50)

linter: added normalization of special KPHP types for PHPDoc


Patch:
diff --git a/src/linter/block.go b/src/linter/block.go
index b2f51a5..25f517e 100644
--- a/src/linter/block.go
+++ b/src/linter/block.go
@@ -247,7 +247,7 @@ func (b *blockWalker) handleCommentToken(n ir.Node, t *token.Token) {
 			continue
 		}
 
-		converted := phpdoctypes.ToRealType(b.r.ctx.typeNormalizer.ClassFQNProvider(), part.Type)
+		converted := phpdoctypes.ToRealType(b.r.ctx.typeNormalizer.ClassFQNProvider(), b.r.config.KPHP, part.Type)
 		moveShapesToContext(&b.r.ctx, converted.Shapes)
 		b.r.handleClosuresFromDoc(converted.Closures)
 
diff --git a/src/linter/phpdoc_util.go b/src/linter/phpdoc_util.go
index f227ec5..6f07196 100644
--- a/src/linter/phpdoc_util.go
+++ b/src/linter/phpdoc_util.go
@@ -109,7 +109,7 @@ func parseClassPHPDocMethod(classNode ir.Node, ctx *rootContext, result *classPH
 	}
 
 	typ := ctx.phpdocTypeParser.Parse(params[0])
-	converted := phpdoctypes.ToRealType(ctx.typeNormalizer.ClassFQNProvider(), typ)
+	converted := phpdoctypes.ToRealType(ctx.typeNormalizer.ClassFQNProvider(), ctx.typeNormalizer.KPHP(), typ)
 	moveShapesToContext(ctx, converted.Shapes)
 
 	if converted.Warning != "" {
@@ -172,7 +172,7 @@ func parseClassPHPDocProperty(classNode ir.Node, ctx *rootContext, result *class
 		)
 	}
 
-	converted := phpdoctypes.ToRealType(ctx.typeNormalizer.ClassFQNProvider(), part.Type)
+	converted := phpdoctypes.ToRealType(ctx.typeNormalizer.ClassFQNProvider(), ctx.typeNormalizer.KPHP(), part.Type)
 	moveShapesToContext(ctx, converted.Shapes)
 
 	if converted.Warning != "" {
diff --git a/src/linter/root.go b/src/linter/root.go
index 7860105..444f0b5 100644
--- a/src/linter/root.go
+++ b/src/linter/root.go
@@ -851,7 +851,7 @@ func (d *rootWalker) parsePHPDocVar(doc phpdoc.Comment) (typesMap types.Map) {
 	for _, part := range doc.Parsed {
 		part, ok := part.(*phpdoc.TypeVarCommentPart)
 		if ok && part.Name() == "var" {
-			converted := phpdoctypes.ToRealType(d.ctx.typeNormalizer.ClassFQNProvider(), part.Type)
+			converted := phpdoctypes.ToRealType(d.ctx.typeNormalizer.ClassFQNProvider(), d.config.KPHP, part.Type)
 			moveShapesToContext(&d.ctx, converted.Shapes)
 			d.handleClosuresFromDoc(converted.Closures)
 
diff --git a/src/linter/root_checker.go b/src/linter/root_checker.go
index 30d9350..40763c1 100644
--- a/src/linter/root_checker.go
+++ b/src/linter/root_checker.go
@@ -171,7 +171,7 @@ func (r *rootChecker) CheckPHPDoc(n ir.Node, doc phpdoc.Comment, actualParams []
 		if rawPart.Name() == "return" {
 			part := rawPart.(*phpdoc.TypeCommentPart)
 
-			converted := phpdoctypes.ToRealType(r.normalizer.ClassFQNProvider(), part.Type)
+			converted := phpdoctypes.ToRealType(r.normalizer.ClassFQNProvider(), r.normalizer.KPHP(), part.Type)
 
 			if converted.Warning != "" {
 				errors.pushType(
@@ -240,7 +240,7 @@ func (r *rootChecker) CheckPHPDoc(n ir.Node, doc phpdoc.Comment, actualParams []
 
 		curParam++
 
-		converted := phpdoctypes.ToRealType(r.normalizer.ClassFQNProvider(), part.Type)
+		converted := phpdoctypes.ToRealType(r.normalizer.ClassFQNProvider(), r.normalizer.KPHP(), part.Type)
 
 		if converted.Warning != "" {
 			errors.pushType(
@@ -731,7 +731,7 @@ func (r *rootChecker) CheckPHPDocVar(n ir.Node, doc phpdoc.Comment, typ types.Ma
 		r.checkPHPDocRef(n, part)
 		part, ok := part.(*phpdoc.TypeVarCommentPart)
 		if ok && part.Name() == "var" {
-			converted := phpdoctypes.ToRealType(r.normalizer.ClassFQNProvider(), part.Type)
+			converted := phpdoctypes.ToRealType(r.normalizer.ClassFQNProvider(), r.normalizer.KPHP(), part.Type)
 
 			if converted.Warning != "" {
 				field := 1
diff --git a/src/phpdoctypes/converter.go b/src/phpdoctypes/converter.go
index a2c7dbe..d3f6f6d 100644
--- a/src/phpdoctypes/converter.go
+++ b/src/phpdoctypes/converter.go
@@ -20,12 +20,13 @@ type RealPHPDocTypes struct {
 //
 // No normalization is performed, but some PHPDoc-specific types
 // are simplified to be compatible with our type system.
-func ToRealType(classFQNProvider func(string) (string, bool), typ phpdoc.Type) *RealPHPDocTypes {
+func ToRealType(classFQNProvider func(string) (string, bool), kphp bool, typ phpdoc.Type) *RealPHPDocTypes {
 	conv := TypeConverter{
 		classFQNProvider:   classFQNProvider,
 		shapeNameGenerator: autogen.GenerateShapeName,
 		shapes:             make(types.ShapesMap),
 		closures:           make(types.ClosureMap),
+		kphp:               kphp,
 	}
 
 	result := conv.mapType(typ.Expr)
@@ -60,6 +61,7 @@ type TypeConverter struct {
 	nullable           bool
 	shapes             types.ShapesMap
 	closures           types.ClosureMap
+	kphp               bool
 }
 
 func (conv *TypeConverter) mapType(e phpdoc.TypeExpr) []types.Type {
@@ -245,6 +247,10 @@ func (conv *TypeConverter) mapShapeType(params []phpdoc.TypeExpr, allowedMixing
 				continue
 			}
 
+			if conv.kphp && (typ.Elem == "any" || typ.Elem == "kmixed" || typ.Elem == "future") {
+				continue
+			}
+
 			if conv.classFQNProvider == nil {
 				continue
 			}
diff --git a/src/phpdoctypes/parser.go b/src/phpdoctypes/parser.go
index 887c5d2..6ce89c4 100644
--- a/src/phpdoctypes/parser.go
+++ b/src/phpdoctypes/parser.go
@@ -67,7 +67,7 @@ func Parse(doc phpdoc.Comment, actualParams []ir.Node, normalizer types.Normaliz
 		if rawPart.Name() == "return" {
 			part := rawPart.(*phpdoc.TypeCommentPart)
 
-			converted := ToRealType(normalizer.ClassFQNProvider(), part.Type)
+			converted := ToRealType(normalizer.ClassFQNProvider(), normalizer.KPHP(), part.Type)
 			for name, shape := range converted.Shapes {
 				result.Shapes[name] = shape
 			}
@@ -97,7 +97,7 @@ func Parse(doc phpdoc.Comment, actualParams []ir.Node, normalizer types.Normaliz
 
 		curParam++
 
-		converted := ToRealType(normalizer.ClassFQNProvider(), part.Type)
+		converted := ToRealType(normalizer.ClassFQNProvider(), normalizer.KPHP(), part.Type)
 		for name, shape := range converted.Shapes {
 			result.Shapes[name] = shape
 		}
diff --git a/src/types/normalizer.go b/src/types/normalizer.go
index bb3098e..b21ae4e 100644
--- a/src/types/normalizer.go
+++ b/src/types/normalizer.go
@@ -9,6 +9,10 @@ type Normalizer struct {
 	kphp             bool
 }
 
+func (n Normalizer) KPHP() bool {
+	return n.kphp
+}
+
 func NewNormalizer(classFQNProvider func(string) (string, bool), kphp bool) Normalizer {
 	return Normalizer{classFQNProvider: classFQNProvider, kphp: kphp}
 }


Test patch:
diff --git a/src/linter/phpdoc_test.go b/src/linter/phpdoc_test.go
index eea9565..8763c19 100644
--- a/src/linter/phpdoc_test.go
+++ b/src/linter/phpdoc_test.go
@@ -125,7 +125,7 @@ func BenchmarkParseTypes(b *testing.B) {
 	for i := 0; i < b.N; i++ {
 		parsedType := ctx.phpdocTypeParser.Parse(typeString)
 
-		converted := phpdoctypes.ToRealType(ctx.typeNormalizer.ClassFQNProvider(), parsedType)
+		converted := phpdoctypes.ToRealType(ctx.typeNormalizer.ClassFQNProvider(), ctx.typeNormalizer.KPHP(), parsedType)
 		moveShapesToContext(&ctx, converted.Shapes)
 
 		_ = types.NewMapWithNormalization(ctx.typeNormalizer, converted.Types)
diff --git a/src/tests/checkers/kphp_test.go b/src/tests/checkers/kphp_test.go
index 6e5dbed..c1026da 100644
--- a/src/tests/checkers/kphp_test.go
+++ b/src/tests/checkers/kphp_test.go
@@ -29,8 +29,11 @@ func TestDifferentTypesAsUndefinedClass(t *testing.T) {
  * @param kmixed $c
  * @param any $d
  * @param future<int> $e
+ * @param tuple(?any) $f
+ * @param future<?any> $g
+ * @param shape(foo: ?any, boo: future<int>) $h
  */
-function f($a, $b, $c, $d, $e) {}
+function f($a, $b, $c, $d, $e, $f, $g, $h) {}
 `)
 	test.RunAndMatch()
 }