nebius/SWE-rebench-V2-PRs huggingface dataset:
Repository: spf13/viper
Pull request #811 (2019-12-06 16:24:26)

Fix file path lookup


Patch:
diff --git a/util.go b/util.go
index 952cad4..b788969 100644
--- a/util.go
+++ b/util.go
@@ -114,11 +114,11 @@ func absPathify(inPath string) string {
 	return ""
 }
 
-// Check if File / Directory Exists
+// Check if file Exists
 func exists(fs afero.Fs, path string) (bool, error) {
-	_, err := fs.Stat(path)
+	stat, err := fs.Stat(path)
 	if err == nil {
-		return true, nil
+		return !stat.IsDir(), nil
 	}
 	if os.IsNotExist(err) {
 		return false, nil


Test patch:
diff --git a/viper_test.go b/viper_test.go
index 8f010c7..fcd1b0e 100644
--- a/viper_test.go
+++ b/viper_test.go
@@ -323,6 +323,26 @@ func TestSearchInPath(t *testing.T) {
 	assert.NoError(t, err)
 }
 
+func TestSearchInPath_FilesOnly(t *testing.T) {
+	fs := afero.NewMemMapFs()
+
+	err := fs.Mkdir("/tmp/config", 0777)
+	require.NoError(t, err)
+
+	_, err = fs.Create("/tmp/config/config.yaml")
+	require.NoError(t, err)
+
+	v := New()
+
+	v.SetFs(fs)
+	v.AddConfigPath("/tmp")
+	v.AddConfigPath("/tmp/config")
+
+	filename, err := v.getConfigFile()
+	assert.Equal(t, "/tmp/config/config.yaml", filename)
+	assert.NoError(t, err)
+}
+
 func TestDefault(t *testing.T) {
 	SetDefault("age", 45)
 	assert.Equal(t, 45, Get("age"))
Repository: spf13/viper
Pull request #827 (2020-01-12 10:26:22)

Extensionless files only allowed when config type is set
This PR addresses #818 

cc @andig

andig: @sagikazarmark could we get this in and preferably release to unbreak 1.6 for this use case?

Patch:
diff --git a/README.md b/README.md
index 2bb54fa..a574677 100644
--- a/README.md
+++ b/README.md
@@ -101,6 +101,7 @@ where a configuration file is expected.
 
 ```go
 viper.SetConfigName("config") // name of config file (without extension)
+viper.SetConfigType("yaml") // REQUIRED if the config file does not have the extension in the name
 viper.AddConfigPath("/etc/appname/")   // path to look for the config file in
 viper.AddConfigPath("$HOME/.appname")  // call multiple times to add many search paths
 viper.AddConfigPath(".")               // optionally look for config in the working directory
@@ -124,7 +125,7 @@ if err := viper.ReadInConfig(); err != nil {
 // Config file found and successfully parsed
 ```
 
-*NOTE:* You can also have a file without an extension and specify the format programmaticaly. For those configuration files that lie in the home of the user without any extension like `.bashrc`
+*NOTE [since 1.6]:* You can also have a file without an extension and specify the format programmaticaly. For those configuration files that lie in the home of the user without any extension like `.bashrc`
 
 ### Writing Config Files
 
diff --git a/viper.go b/viper.go
index eb2f517..265eae7 100644
--- a/viper.go
+++ b/viper.go
@@ -1976,8 +1976,10 @@ func (v *Viper) searchInPath(in string) (filename string) {
 		}
 	}
 
-	if b, _ := exists(v.fs, filepath.Join(in, v.configName)); b {
-		return filepath.Join(in, v.configName)
+	if v.configType != "" {
+		if b, _ := exists(v.fs, filepath.Join(in, v.configName)); b {
+			return filepath.Join(in, v.configName)
+		}
 	}
 
 	return ""


Test patch:
diff --git a/viper_test.go b/viper_test.go
index fcd1b0e..f9dc34a 100644
--- a/viper_test.go
+++ b/viper_test.go
@@ -307,11 +307,29 @@ func TestBasics(t *testing.T) {
 	assert.NoError(t, err)
 }
 
+func TestSearchInPath_WithoutConfigTypeSet(t *testing.T) {
+	filename := ".dotfilenoext"
+	path := "/tmp"
+	file := filepath.Join(path, filename)
+	SetConfigName(filename)
+	AddConfigPath(path)
+	_, createErr := v.fs.Create(file)
+	defer func() {
+		_ = v.fs.Remove(file)
+	}()
+	assert.NoError(t, createErr)
+	_, err := v.getConfigFile()
+	// unless config type is set, files without extension
+	// are not considered
+	assert.Error(t, err)
+}
+
 func TestSearchInPath(t *testing.T) {
 	filename := ".dotfilenoext"
 	path := "/tmp"
 	file := filepath.Join(path, filename)
 	SetConfigName(filename)
+	SetConfigType("yaml")
 	AddConfigPath(path)
 	_, createErr := v.fs.Create(file)
 	defer func() {
Repository: spf13/viper
Pull request #940 (2020-07-31 20:11:45)

Fix abs pathify
I've created a new PR to address the path expansion issue first reported by @biochimia in #495.

This PR simply adds requested changes to speed things along. CC: @sagikazarmark 

Fixes #495

CLAassistant: [![CLA assistant check](https://cla-assistant.io/pull/badge/not_signed)](https://cla-assistant.io/spf13/viper?pullRequest=940) <br/>Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our [Contributor License Agreement](https://cla-assistant.io/spf13/viper?pullRequest=940) before we can accept your contribution.<br/>**1** out of **2** committers have signed the CLA.<br/><br/>:white_check_mark: biochimia<br/>:x: qjcg<br/><sub>You have signed the CLA already but the status is still pending? Let us [recheck](https://cla-assistant.io/check/spf13/viper?pullRequest=940) it.</sub>

Patch:
diff --git a/util.go b/util.go
index b788969..cee6b24 100644
--- a/util.go
+++ b/util.go
@@ -91,13 +91,22 @@ func insensitiviseMap(m map[string]interface{}) {
 func absPathify(inPath string) string {
 	jww.INFO.Println("Trying to resolve absolute path to", inPath)
 
-	if strings.HasPrefix(inPath, "$HOME") {
+	if inPath == "$HOME" || strings.HasPrefix(inPath, "$HOME"+string(os.PathSeparator)) {
 		inPath = userHomeDir() + inPath[5:]
 	}
 
 	if strings.HasPrefix(inPath, "$") {
 		end := strings.Index(inPath, string(os.PathSeparator))
-		inPath = os.Getenv(inPath[1:end]) + inPath[end:]
+
+		var value, suffix string
+		if end == -1 {
+			value = os.Getenv(inPath[1:])
+		} else {
+			value = os.Getenv(inPath[1:end])
+			suffix = inPath[end:]
+		}
+
+		inPath = value + suffix
 	}
 
 	if filepath.IsAbs(inPath) {


Test patch:
diff --git a/util_test.go b/util_test.go
index 0af80bb..01d932d 100644
--- a/util_test.go
+++ b/util_test.go
@@ -11,6 +11,8 @@
 package viper
 
 import (
+	"os"
+	"path/filepath"
 	"reflect"
 	"testing"
 )
@@ -52,3 +54,38 @@ func TestCopyAndInsensitiviseMap(t *testing.T) {
 		t.Fatal("Input map changed")
 	}
 }
+
+func TestAbsPathify(t *testing.T) {
+	home := userHomeDir()
+	homer := filepath.Join(home, "homer")
+	wd, _ := os.Getwd()
+
+	os.Setenv("HOMER_ABSOLUTE_PATH", homer)
+	os.Setenv("VAR_WITH_RELATIVE_PATH", "relative")
+
+	tests := []struct {
+		input  string
+		output string
+	}{
+		{"", wd},
+		{"sub", filepath.Join(wd, "sub")},
+		{"./", wd},
+		{"./sub", filepath.Join(wd, "sub")},
+		{"$HOME", home},
+		{"$HOME/", home},
+		{"$HOME/sub", filepath.Join(home, "sub")},
+		{"$HOMER_ABSOLUTE_PATH", homer},
+		{"$HOMER_ABSOLUTE_PATH/", homer},
+		{"$HOMER_ABSOLUTE_PATH/sub", filepath.Join(homer, "sub")},
+		{"$VAR_WITH_RELATIVE_PATH", filepath.Join(wd, "relative")},
+		{"$VAR_WITH_RELATIVE_PATH/", filepath.Join(wd, "relative")},
+		{"$VAR_WITH_RELATIVE_PATH/sub", filepath.Join(wd, "relative", "sub")},
+	}
+
+	for _, test := range tests {
+		got := absPathify(test.input)
+		if got != test.output {
+			t.Errorf("Got %v\nexpected\n%q", got, test.output)
+		}
+	}
+}
Repository: spf13/viper
Pull request #1405 (2022-07-19 08:00:57)

Adds support for uint16 with `GetUint16`
We have encountered numerous places where it is convenient to have viper return a `uint16` value, especially in combination with the new `netip` package  that represents a port correctly as `uint16` rather than just an `int`. cobra already supports this, but we need a conversion from the existing `GetUint` method in viper.


CLAassistant: [![CLA assistant check](https://cla-assistant.io/pull/badge/not_signed)](https://cla-assistant.io/spf13/viper?pullRequest=1405) <br/>Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our [Contributor License Agreement](https://cla-assistant.io/spf13/viper?pullRequest=1405) before we can accept your contribution.<br/><sub>You have signed the CLA already but the status is still pending? Let us [recheck](https://cla-assistant.io/check/spf13/viper?pullRequest=1405) it.</sub>
oxisto: > @oxisto actually, can you please rebase your PR to trigger CI?

done.

Patch:
diff --git a/viper.go b/viper.go
index af6ee69..9f7e90d 100644
--- a/viper.go
+++ b/viper.go
@@ -991,6 +991,13 @@ func (v *Viper) GetUint(key string) uint {
 	return cast.ToUint(v.Get(key))
 }
 
+// GetUint16 returns the value associated with the key as an unsigned integer.
+func GetUint16(key string) uint16 { return v.GetUint16(key) }
+
+func (v *Viper) GetUint16(key string) uint16 {
+	return cast.ToUint16(v.Get(key))
+}
+
 // GetUint32 returns the value associated with the key as an unsigned integer.
 func GetUint32(key string) uint32 { return v.GetUint32(key) }
 


Test patch:
diff --git a/viper_test.go b/viper_test.go
index 5c6c809..926ffc2 100644
--- a/viper_test.go
+++ b/viper_test.go
@@ -1953,6 +1953,10 @@ func TestMergeConfig(t *testing.T) {
 		t.Fatalf("uint pop != 37890, = %d", pop)
 	}
 
+	if pop := v.GetUint16("hello.pop"); pop != uint16(37890) {
+		t.Fatalf("uint pop != 37890, = %d", pop)
+	}
+
 	if pop := v.GetUint32("hello.pop"); pop != 37890 {
 		t.Fatalf("uint32 pop != 37890, = %d", pop)
 	}
Repository: spf13/viper
Pull request #1704 (2023-12-06 21:26:32)

Merge missing struct keys inside UnmarshalExact
This is a follow-up PR after #1429. A [comment](https://github.com/spf13/viper/pull/1429#issuecomment-1843183656) mentioned `UnmarshalExact` not working as intended. Looking into the code I found that the list of struct keys was not merged with `AllKeys` inside `UnmarshalExact`, leading to the mentioned error.

This PR fixes this by performing the same steps in `UnmarshalExact`.

@sagikazarmark 


Patch:
diff --git a/viper.go b/viper.go
index 714b2f2..122280b 100644
--- a/viper.go
+++ b/viper.go
@@ -1179,7 +1179,14 @@ func (v *Viper) UnmarshalExact(rawVal any, opts ...DecoderConfigOption) error {
 	config := defaultDecoderConfig(rawVal, opts...)
 	config.ErrorUnused = true
 
-	return decode(v.AllSettings(), config)
+	// TODO: make this optional?
+	structKeys, err := v.decodeStructKeys(rawVal, opts...)
+	if err != nil {
+		return err
+	}
+
+	// TODO: struct keys should be enough?
+	return decode(v.getSettings(append(v.AllKeys(), structKeys...)), config)
 }
 
 // BindPFlags binds a full flag set to the configuration, using each flag's long


Test patch:
diff --git a/viper_test.go b/viper_test.go
index 98c379d..4dc52ac 100644
--- a/viper_test.go
+++ b/viper_test.go
@@ -1052,6 +1052,32 @@ func TestUnmarshalWithAutomaticEnv(t *testing.T) {
 
 		assert.Error(t, err, "expected viper.Unmarshal to return error due to unset field 'FLAG'")
 	})
+
+	t.Run("Exact", func(t *testing.T) {
+		var config Configuration
+
+		v.Set("port", 1234)
+		if err := v.UnmarshalExact(&config); err != nil {
+			t.Fatalf("unable to decode into struct, %v", err)
+		}
+
+		assert.Equal(
+			t,
+			Configuration{
+				Name:     "Steve",
+				Port:     1234,
+				Duration: time.Second + time.Millisecond,
+				Modes:    []int{1, 2, 3},
+				Authentication: AuthConfig{
+					Secret: "42",
+				},
+				Storage: StorageConfig{
+					Size: 4096,
+				},
+			},
+			config,
+		)
+	})
 }
 
 func TestBindPFlags(t *testing.T) {
Repository: VKCOM/noverify
Pull request #567 (2020-07-08 14:42:18)

cmd: add missing file.Close() + return Stats from baseline.ReadProfile
Signed-off-by: Iskander Sharipov <quasilyte@gmail.com>


Patch:
diff --git a/src/baseline/baseline.go b/src/baseline/baseline.go
index faf1e58..43c7c85 100644
--- a/src/baseline/baseline.go
+++ b/src/baseline/baseline.go
@@ -66,15 +66,15 @@ type Report struct {
 }
 
 // ReadProfile reads a suppression profile from a provided reader.
-func ReadProfile(r io.Reader) (*Profile, error) {
+func ReadProfile(r io.Reader) (*Profile, *Stats, error) {
 	var p jsonProfile
 	dec := json.NewDecoder(r)
 	if err := dec.Decode(&p); err != nil {
-		return nil, fmt.Errorf("can't decode baseline file: %v (version mismatch?)", err)
+		return nil, nil, fmt.Errorf("can't decode baseline file: %v (version mismatch?)", err)
 	}
 
 	if p.Version != profileVersion {
-		return nil, fmt.Errorf("version mismatch: want %d, have %d", p.Version, profileVersion)
+		return nil, nil, fmt.Errorf("version mismatch: want %d, have %d", p.Version, profileVersion)
 	}
 
 	files := make(map[string]FileProfile, len(p.Files))
@@ -90,7 +90,7 @@ func ReadProfile(r io.Reader) (*Profile, error) {
 					hashPart = parts[0]
 					r.Count, err = strconv.Atoi(parts[1])
 					if err != nil {
-						return nil, fmt.Errorf("%s: parse hash count: %v", f.Filename, err)
+						return nil, nil, fmt.Errorf("%s: parse hash count: %v", f.Filename, err)
 					}
 				} else {
 					r.Count = 1
@@ -98,7 +98,7 @@ func ReadProfile(r io.Reader) (*Profile, error) {
 				}
 				r.Hash, err = strconv.ParseUint(hashPart, 36, 64)
 				if err != nil {
-					return nil, fmt.Errorf("%s: parse hash: %v", f.Filename, err)
+					return nil, nil, fmt.Errorf("%s: parse hash: %v", f.Filename, err)
 				}
 				reports[r.Hash] = r
 			}
@@ -114,7 +114,7 @@ func ReadProfile(r io.Reader) (*Profile, error) {
 		LinterVersion: p.LinterVersion,
 		Files:         files,
 	}
-	return result, nil
+	return result, p.Stats, nil
 }
 
 // WriteProfile writes a given suppression profile to w.
diff --git a/src/cmd/linter_runner.go b/src/cmd/linter_runner.go
index 709e61e..79218de 100644
--- a/src/cmd/linter_runner.go
+++ b/src/cmd/linter_runner.go
@@ -83,7 +83,8 @@ func (l *linterRunner) initBaseline() error {
 	if err != nil {
 		return err
 	}
-	profile, err := baseline.ReadProfile(f)
+	defer f.Close()
+	profile, _, err := baseline.ReadProfile(f)
 	if err != nil {
 		return err
 	}


Test patch:
diff --git a/src/baseline/baseline_test.go b/src/baseline/baseline_test.go
index 4c3fc94..21946f1 100644
--- a/src/baseline/baseline_test.go
+++ b/src/baseline/baseline_test.go
@@ -159,7 +159,7 @@ func TestWriteReadBaseline(t *testing.T) {
 			t.Fatalf("iter=%d printed output differs:\n%s", i, diff)
 		}
 
-		y, err := ReadProfile(&buf)
+		y, _, err := ReadProfile(&buf)
 		if err != nil {
 			t.Fatalf("iter=%d error while reading encoded profile: %v", i, err)
 		}
Repository: VKCOM/noverify
Pull request #582 (2020-07-14 17:53:34)

Solve: handle prefix/infix ++ and -- in exprtype #462
Solve #462


Patch:
diff --git a/src/solver/exprtype.go b/src/solver/exprtype.go
index c5ae10f..9321c72 100644
--- a/src/solver/exprtype.go
+++ b/src/solver/exprtype.go
@@ -324,6 +324,14 @@ func exprTypeLocalCustom(sc *meta.Scope, cs *meta.ClassParseState, n node.Node,
 		return binaryMathOpType(sc, cs, n.Left, n.Right, custom)
 	case *binary.Mod:
 		return binaryMathOpType(sc, cs, n.Left, n.Right, custom)
+	case *expr.PostInc:
+		return unaryMathOpType(sc, cs, n.Variable, custom)
+	case *expr.PreInc:
+		return unaryMathOpType(sc, cs, n.Variable, custom)
+	case *expr.PostDec:
+		return unaryMathOpType(sc, cs, n.Variable, custom)
+	case *expr.PreDec:
+		return unaryMathOpType(sc, cs, n.Variable, custom)
 	case *cast.Array:
 		return meta.NewTypesMap("mixed[]")
 	case *cast.Bool:


Test patch:
diff --git a/src/linttest/exprtype_test.go b/src/linttest/exprtype_test.go
index b4de9bb..39e8087 100644
--- a/src/linttest/exprtype_test.go
+++ b/src/linttest/exprtype_test.go
@@ -1752,6 +1752,58 @@ exprtype(array_map(function($x) { return $x; }, $ints), 'mixed[]');
 	runExprTypeTest(t, &exprTypeTestParams{stubs: stubs, code: code})
 }
 
+func TestPostfixPrefixIncDec(t *testing.T) {
+	code := `<?php
+
+$a = 100;
+
+exprtype($a++, "int");
+exprtype($a--, "int");
+exprtype(++$a, "int");
+exprtype(--$a, "int");
+
+
+$a = 100.5;
+
+exprtype($a++, "float");
+exprtype($a--, "float");
+exprtype(++$a, "float");
+exprtype(--$a, "float");
+
+
+$a = "100";
+
+exprtype($a++, "float");
+exprtype($a--, "float");
+exprtype(++$a, "float");
+exprtype(--$a, "float");
+
+
+if ($a == 100) {
+	$a = 56.5
+} else {
+	$a = 56
+}
+
+exprtype($a++, "float");
+exprtype($a--, "float");
+exprtype(++$a, "float");
+exprtype(--$a, "float");
+
+
+class Foo {};
+
+$a = new Foo();
+
+exprtype($a++, "float");
+exprtype($a--, "float");
+exprtype(++$a, "float");
+exprtype(--$a, "float");
+
+`
+	runExprTypeTest(t, &exprTypeTestParams{code: code})
+}
+
 func runExprTypeTest(t *testing.T, params *exprTypeTestParams) {
 	meta.ResetInfo()
 	if params.stubs != "" {
Repository: VKCOM/noverify
Pull request #639 (2020-08-06 12:08:06)

linter: added handling of type hints for class property

quasilyte: Please add tests for type combining when phpdoc present as well. You have append for types, so they are expected to become a union type. As a corner case, `T` + `T` should remain `T` (this is handled by maps automatically, but it's nice to have a test for it).

Patch:
diff --git a/src/linter/root.go b/src/linter/root.go
index 6f74b19..5b3150c 100644
--- a/src/linter/root.go
+++ b/src/linter/root.go
@@ -892,6 +892,11 @@ func (d *RootWalker) enterPropertyList(pl *stmt.PropertyList) bool {
 		}
 	}
 
+	var specifiedType meta.TypesMap
+	if typ, ok := d.parseTypeNode(pl.Type); ok {
+		specifiedType = typ
+	}
+
 	for _, pNode := range pl.Properties {
 		p := pNode.(*stmt.Property)
 
@@ -902,6 +907,7 @@ func (d *RootWalker) enterPropertyList(pl *stmt.PropertyList) bool {
 		if p.Expr != nil {
 			typ = typ.Append(solver.ExprTypeLocal(d.scope(), d.ctx.st, p.Expr))
 		}
+		typ = typ.Append(specifiedType)
 
 		if isStatic {
 			nm = "$" + nm


Test patch:
diff --git a/src/tests/exprtype/exprtype_test.go b/src/tests/exprtype/exprtype_test.go
index 63e2476..baba403 100644
--- a/src/tests/exprtype/exprtype_test.go
+++ b/src/tests/exprtype/exprtype_test.go
@@ -1868,6 +1868,91 @@ exprtype($tuple_int_str, "unknown_from_list");
 	runExprTypeTest(t, &exprTypeTestParams{code: code})
 }
 
+func TestPropertyTypeHints(t *testing.T) {
+	code := `<?php
+class Too {}
+class Boo {}
+
+// simple use
+class Foo {
+  public int $int;
+  public array $array;
+  public Boo $boo;
+
+  public static float $float;
+  public static object $object;
+  public static Too $too;
+}
+
+$f = new Foo();
+
+exprtype($f->int, "int");
+exprtype($f->array, "mixed[]");
+exprtype($f->boo, "\Boo");
+
+exprtype(Foo::$float, "float");
+exprtype(Foo::$object, "object");
+exprtype(Foo::$too, "\Too");
+
+
+// with PHPDoc
+class Poo {
+  /** @var float $int */
+  public static int $int;
+
+  /** @var array $callable */
+  public object $object;
+
+  /** @var array $array */
+  public array $array;
+
+  /** @var float|int $a */
+  public static int $a;
+
+  /** @var int $b */
+  public int $b;
+
+  /** @var Foo|Too $c */
+  public Boo $c;
+
+  /** @var Boo $d */
+  public Boo $d;
+}
+
+$p = new Poo();
+
+exprtype(Poo::$int, "float|int");
+exprtype($p->object, "mixed[]|object");
+exprtype($p->array, "mixed[]");
+exprtype(Poo::$a, "float|int");
+exprtype($p->b, "int");
+exprtype($p->c, "\Boo|\Foo|\Too");
+exprtype($p->d, "\Boo");
+
+
+// with default value
+class Roo {
+  /** @var float $a */
+  public static int $a = 10;
+
+  /** @var array $b */
+  public array $b = [1,2,3];
+
+  public bool $c = true;
+
+  public static string $d = "Hello";
+}
+
+$r = new Roo();
+
+exprtype(Roo::$a, "float|int");
+exprtype($r->b, "int[]|mixed[]");
+exprtype($r->c, "bool");
+exprtype(Roo::$d, "string");
+`
+	runExprTypeTest(t, &exprTypeTestParams{code: code})
+}
+
 func runExprTypeTest(t *testing.T, params *exprTypeTestParams) {
 	meta.ResetInfo()
 	if params.stubs != "" {
Repository: VKCOM/noverify
Pull request #663 (2020-08-12 10:34:50)

linter: added processing of array unpacking (...$arr)

quasilyte: I think it's a solver package change, not linter package change. I'll edit the commit message.

Patch:
diff --git a/src/meta/typesmap.go b/src/meta/typesmap.go
index e74dacd..179ad06 100644
--- a/src/meta/typesmap.go
+++ b/src/meta/typesmap.go
@@ -376,3 +376,17 @@ func (m TypesMap) Iterate(cb func(typ string)) {
 		cb(k)
 	}
 }
+
+// Returns type of array element. T[] -> T, T[][] -> T[].
+// For *Lazy* type.
+func (m TypesMap) ArrayElemLazyType() TypesMap {
+	if m.Len() == 0 {
+		return MixedType
+	}
+
+	mm := make(map[string]struct{}, m.Len())
+	for typ := range m.m {
+		mm[UnwrapArrayOf(typ)] = struct{}{}
+	}
+	return TypesMap{m: mm, flags: m.flags}
+}
diff --git a/src/solver/exprtype.go b/src/solver/exprtype.go
index f2809fa..3c6de37 100644
--- a/src/solver/exprtype.go
+++ b/src/solver/exprtype.go
@@ -107,9 +107,16 @@ func arrayType(sc *meta.Scope, cs *meta.ClassParseState, items []*ir.ArrayItemEx
 	}
 
 	firstElementType := ExprTypeLocal(sc, cs, items[0])
+	if items[0].Unpack {
+		firstElementType = firstElementType.ArrayElemLazyType()
+	}
 
 	for _, item := range items[1:] {
 		itemType := ExprTypeLocal(sc, cs, item)
+		if item.Unpack {
+			itemType = itemType.ArrayElemLazyType()
+		}
+
 		if !firstElementType.Equals(itemType) {
 			return meta.NewTypesMap("mixed[]")
 		}


Test patch:
diff --git a/src/tests/exprtype/exprtype_test.go b/src/tests/exprtype/exprtype_test.go
index 6e02789..fa5e918 100644
--- a/src/tests/exprtype/exprtype_test.go
+++ b/src/tests/exprtype/exprtype_test.go
@@ -1935,6 +1935,61 @@ exprtype($a, "mixed[]");
 	runExprTypeTest(t, &exprTypeTestParams{code: code})
 }
 
+func TestArrayTypesWithUnpack(t *testing.T) {
+	code := `<?php
+class Foo {}
+
+/** @return Boo[] */
+function returnBooArray() {}
+
+function f() {
+  // with simple type
+  $a = [1,2,3];
+  $b = [0, ...$a];
+  exprtype($b, "int[]");
+
+
+  // with class type
+  $a1 = [new Foo(), new Foo()];
+  $b1 = [new Foo(), ...$a1];
+  exprtype($b1, "\Foo[]");
+
+
+  // different types
+  $a2 = [new Foo(), new Foo()];
+  $b2 = [new Foo(), ...returnBooArray()];
+  exprtype($b2, "mixed[]");
+
+
+  // with two unpack
+  $a3 = [new Foo(), new Foo()];
+  $b3 = [new Foo(), new Foo()];
+  $c3 = [...$a3, ...$b3];
+  exprtype($c3, "\Foo[]");
+
+
+  // with two unpack with different type
+  $a4 = [new Foo(), new Foo()];
+  $c4 = [...$a4, ...returnBooArray()];
+  exprtype($c4, "mixed[]");
+
+
+  // one unpack
+  $a5 = [new Foo(), new Foo()];
+  $b5 = [...$a5];
+  exprtype($b5, "\Foo[]");
+
+
+  // with two unpack and just type
+  $a6 = [new Foo(), new Foo()];
+  $b6 = [new Foo(), new Foo()];
+  $c6 = [...$a6, new Foo(),...$b6];
+  exprtype($c6, "\Foo[]");
+}
+`
+	runExprTypeTest(t, &exprTypeTestParams{code: code})
+}
+
 func TestPropertyTypeHints(t *testing.T) {
 	code := `<?php
 class Too {}
Repository: VKCOM/noverify
Pull request #665 (2020-08-12 22:35:42)

ir/irfmt: print new expr without () if Args==nil
This is consistent with what we had before the IR transition.

Signed-off-by: Iskander Sharipov <quasilyte@gmail.com>


Patch:
diff --git a/src/ir/irfmt/printer.go b/src/ir/irfmt/printer.go
index 8cdb52e..338abca 100644
--- a/src/ir/irfmt/printer.go
+++ b/src/ir/irfmt/printer.go
@@ -948,7 +948,7 @@ func (p *PrettyPrinter) printExprNew(n *ir.NewExpr) {
 	io.WriteString(p.w, "new ")
 	p.Print(n.Class)
 
-	if len(n.Args) != 0 {
+	if n.Args != nil {
 		io.WriteString(p.w, "(")
 		p.joinPrint(", ", n.Args)
 		io.WriteString(p.w, ")")


Test patch:
diff --git a/src/ir/irfmt/printer_test.go b/src/ir/irfmt/printer_test.go
index 4f60f96..b3b1b57 100644
--- a/src/ir/irfmt/printer_test.go
+++ b/src/ir/irfmt/printer_test.go
@@ -1600,6 +1600,37 @@ func TestPrintNew(t *testing.T) {
 	}
 }
 
+func TestPrintNewNoParens(t *testing.T) {
+	o := bytes.NewBufferString("")
+
+	p := NewPrettyPrinter(o, "    ")
+	p.Print(&ir.NewExpr{Class: &ir.Name{Value: "Foo"}})
+
+	expected := `new Foo`
+	actual := o.String()
+
+	if expected != actual {
+		t.Errorf("\nexpected: %s\ngot: %s\n", expected, actual)
+	}
+}
+
+func TestPrintNewWithParens(t *testing.T) {
+	o := bytes.NewBufferString("")
+
+	p := NewPrettyPrinter(o, "    ")
+	p.Print(&ir.NewExpr{
+		Class: &ir.Name{Value: "Foo"},
+		Args:  []ir.Node{},
+	})
+
+	expected := `new Foo()`
+	actual := o.String()
+
+	if expected != actual {
+		t.Errorf("\nexpected: %s\ngot: %s\n", expected, actual)
+	}
+}
+
 func TestPrintPostDec(t *testing.T) {
 	o := bytes.NewBufferString("")