Repository: VKCOM/noverify
Pull request #843 (2020-12-09 14:19:24)

solver: added type inference for KPHP-specific functions array_last_e…
…lement and array_first_element


Patch:
diff --git a/src/linter/parser.go b/src/linter/parser.go
index 118a4f2..887f93c 100644
--- a/src/linter/parser.go
+++ b/src/linter/parser.go
@@ -123,7 +123,7 @@ func ParseFilenames(readFileNamesFunc workspace.ReadCallback, allowDisabled *reg
 
 	var allReports []*Report
 	for i := 0; i < MaxConcurrency; i++ {
-		allReports = append(allReports, (<-reportsCh)...)
+		allReports = append(allReports, <-reportsCh...)
 	}
 
 	return allReports
@@ -133,6 +133,9 @@ func InitStubs(readFileNamesFunc workspace.ReadCallback) {
 	meta.SetLoadingStubs(true)
 	ParseFilenames(readFileNamesFunc, nil)
 	meta.Info.InitStubs()
+	if KPHP {
+		meta.Info.InitKphpStubs()
+	}
 	meta.SetLoadingStubs(false)
 }
 
diff --git a/src/meta/metainfo.go b/src/meta/metainfo.go
index ac8a415..2f412b0 100644
--- a/src/meta/metainfo.go
+++ b/src/meta/metainfo.go
@@ -131,6 +131,30 @@ func (i *info) FindConstants(substr string) (res []string) {
 	return res
 }
 
+func (i *info) InitKphpStubs() {
+	internalFunctions.H[`\array_first_element`] = FuncInfo{
+		Name:         `\array_first_element`,
+		Params:       []FuncParam{{Name: "el"}},
+		MinParamsCnt: 1,
+		Typ:          NewTypesMap("mixed"),
+	}
+	internalFunctions.H[`\array_last_element`] = FuncInfo{
+		Name:         `\array_last_element`,
+		Params:       []FuncParam{{Name: "el"}},
+		MinParamsCnt: 1,
+		Typ:          NewTypesMap("mixed"),
+	}
+
+	internalFunctionOverrides[`\array_first_element`] = FuncInfoOverride{
+		OverrideType: OverrideElementType,
+		ArgNum:       0,
+	}
+	internalFunctionOverrides[`\array_last_element`] = FuncInfoOverride{
+		OverrideType: OverrideElementType,
+		ArgNum:       0,
+	}
+}
+
 func (i *info) InitStubs() {
 	i.Lock()
 	defer i.Unlock()
diff --git a/src/meta/typesmap.go b/src/meta/typesmap.go
index b1e3a06..abbc86a 100644
--- a/src/meta/typesmap.go
+++ b/src/meta/typesmap.go
@@ -377,7 +377,7 @@ func (m TypesMap) Iterate(cb func(typ string)) {
 	}
 }
 
-// Returns type of array element. T[] -> T, T[][] -> T[].
+// ArrayElemLazyType returns type of array element. T[] -> T, T[][] -> T[].
 // For *Lazy* type.
 func (m TypesMap) ArrayElemLazyType() TypesMap {
 	if m.Len() == 0 {


Test patch:
diff --git a/src/tests/exprtype/exprtype_test.go b/src/tests/exprtype/exprtype_test.go
index 4393ca2..e36b042 100644
--- a/src/tests/exprtype/exprtype_test.go
+++ b/src/tests/exprtype/exprtype_test.go
@@ -2617,6 +2617,96 @@ function f() {
 	runExprTypeTest(t, &exprTypeTestParams{code: code})
 }
 
+func TestArrayFirstLastType(t *testing.T) {
+	code := `<?php
+class Foo {}
+
+/**
+ * @return Foo[]
+ */
+function returnFooArray() {
+	return [new Foo, new Foo, new Foo];
+}
+
+/**
+ * @return mixed
+ */
+function returnMixed() {}
+
+/**
+ * @return mixed[]
+ */
+function returnMixedArray() {}
+
+function f() {
+	$a = [10, 20, 30];
+	$b = array_last_element($a);
+	exprtype($b, "int");
+	
+	$c = [new Foo, new Foo, new Foo];
+	$d = array_last_element($c);
+	exprtype($d, "\Foo");
+
+	$e = returnFooArray();
+	$f = array_last_element($e);
+	exprtype($f, "\Foo");
+
+	$g = returnMixed();
+	$h = array_last_element($g);
+	exprtype($h, "mixed");
+
+	$i = returnMixedArray();
+	$j = array_last_element($i);
+	exprtype($j, "mixed");
+
+	$k = array_last_element([10, 20]);
+	exprtype($k, "int");
+
+	$l = array_last_element(20);
+	exprtype($l, "mixed");
+
+	$m = array_last_element();
+	exprtype($m, "mixed");
+}
+
+function f1() {
+	$a = [10, 20, 30];
+	$b = array_first_element($a);
+	exprtype($b, "int");
+	
+	$c = [new Foo, new Foo, new Foo];
+	$d = array_first_element($c);
+	exprtype($d, "\Foo");
+
+	$e = returnFooArray();
+	$f = array_first_element($e);
+	exprtype($f, "\Foo");
+
+	$g = returnMixed();
+	$h = array_first_element($g);
+	exprtype($h, "mixed");
+
+	$i = returnMixedArray();
+	$j = array_first_element($i);
+	exprtype($j, "mixed");
+
+	$k = array_first_element([10, 20]);
+	exprtype($k, "int");
+
+	$l = array_first_element(20);
+	exprtype($l, "mixed");
+
+	$m = array_first_element();
+	exprtype($m, "mixed");
+}
+`
+	linter.KPHP = true
+	defer func() {
+		linter.KPHP = false
+	}()
+	runExprTypeTest(t, &exprTypeTestParams{code: code, stubs: "<?php /* no code */"})
+}
+
 func runExprTypeTest(t *testing.T, params *exprTypeTestParams) {
 	meta.ResetInfo()
 	if params.stubs != "" {