Repository: VKCOM/noverify
Pull request #751 (2020-09-02 09:07:30)

solver: add .= <<= >== assignments type inference
Since these operators always return the precise type,
it's a low hanging fruit for us.

Signed-off-by: Iskander Sharipov <quasilyte@gmail.com>


Patch:
diff --git a/src/linter/block.go b/src/linter/block.go
index e2f1580..7092c8d 100644
--- a/src/linter/block.go
+++ b/src/linter/block.go
@@ -233,6 +233,12 @@ func (b *BlockWalker) EnterNode(n ir.Node) (res bool) {
 		b.handleAssignOp(s)
 	case *ir.AssignDiv:
 		b.handleAssignOp(s)
+	case *ir.AssignConcat:
+		b.handleAssignOp(s)
+	case *ir.AssignShiftLeft:
+		b.handleAssignOp(s)
+	case *ir.AssignShiftRight:
+		b.handleAssignOp(s)
 	case *ir.ArrayExpr:
 		res = b.handleArray(s)
 	case *ir.ForeachStmt:
@@ -1816,6 +1822,19 @@ func (b *BlockWalker) handleAssignOp(assign ir.Node) {
 		}
 		v = assign.Variable
 		typ = solver.ExprTypeLocal(b.ctx.sc, b.r.ctx.st, e)
+
+	case *ir.AssignConcat:
+		v = assign.Variable
+		typ = meta.PreciseStringType
+	case *ir.AssignShiftLeft:
+		v = assign.Variable
+		typ = meta.PreciseIntType
+	case *ir.AssignShiftRight:
+		v = assign.Variable
+		typ = meta.PreciseIntType
+
+	default:
+		return
 	}
 
 	b.replaceVar(v, typ, "assign", meta.VarAlwaysDefined)
diff --git a/src/solver/exprtype.go b/src/solver/exprtype.go
index b5c0de2..5c23db4 100644
--- a/src/solver/exprtype.go
+++ b/src/solver/exprtype.go
@@ -9,6 +9,13 @@ import (
 	"github.com/VKCOM/noverify/src/meta"
 )
 
+func unaryBitwiseOpType(sc *meta.Scope, cs *meta.ClassParseState, x ir.Node, custom []CustomType) meta.TypesMap {
+	if ExprTypeLocalCustom(sc, cs, x, custom).Is("string") {
+		return meta.NewTypesMap("string")
+	}
+	return meta.NewTypesMap("int")
+}
+
 func bitwiseOpType(sc *meta.Scope, cs *meta.ClassParseState, left, right ir.Node, custom []CustomType) meta.TypesMap {
 	if ExprTypeLocalCustom(sc, cs, left, custom).Is("string") && ExprTypeLocalCustom(sc, cs, right, custom).Is("string") {
 		return meta.NewTypesMap("string")
@@ -259,10 +266,7 @@ func exprTypeLocalCustom(sc *meta.Scope, cs *meta.ClassParseState, n ir.Node, cu
 
 		return meta.NewTypesMapFromMap(res)
 	case *ir.BitwiseNotExpr:
-		if ExprTypeLocalCustom(sc, cs, n.Expr, custom).Is("string") {
-			return meta.NewTypesMap("string")
-		}
-		return meta.NewTypesMap("int")
+		return unaryBitwiseOpType(sc, cs, n.Expr, custom)
 	case *ir.BitwiseAndExpr:
 		return bitwiseOpType(sc, cs, n.Left, n.Right, custom)
 	case *ir.BitwiseOrExpr:
@@ -358,8 +362,14 @@ func exprTypeLocalCustom(sc *meta.Scope, cs *meta.ClassParseState, n ir.Node, cu
 		return meta.TypesMap{}
 	case *ir.ParenExpr:
 		return ExprTypeLocalCustom(sc, cs, n.Expr, custom)
+
 	case *ir.Assign:
 		return ExprTypeLocalCustom(sc, cs, n.Expression, custom)
+	case *ir.AssignConcat:
+		return meta.PreciseStringType
+	case *ir.AssignShiftLeft, *ir.AssignShiftRight:
+		return meta.PreciseIntType
+
 	case *ir.CloneExpr:
 		return ExprTypeLocalCustom(sc, cs, n.Expr, custom)
 	case *ir.ClosureExpr:


Test patch:
diff --git a/src/tests/exprtype/exprtype_test.go b/src/tests/exprtype/exprtype_test.go
index c1d864b..dd5116b 100644
--- a/src/tests/exprtype/exprtype_test.go
+++ b/src/tests/exprtype/exprtype_test.go
@@ -2400,6 +2400,17 @@ function f2() {
 
 func TestTypeWithAssignOperators(t *testing.T) {
 	code := `<?php
+function g($x) {
+	exprtype($x <<= 5, 'precise int');
+	exprtype($x, 'precise int');
+
+	exprtype($x .= 'abc', 'precise string');
+	exprtype($x, 'precise string');
+
+	exprtype($x >>= 5, 'precise int');
+	exprtype($x, 'precise int');
+}
+
 function f() {
 	$a = 10;
 	$a += 12.5;