nebius/SWE-rebench-V2-PRs huggingface dataset:
Repository: VKCOM/noverify
Pull request #869 (2020-12-22 12:06:35)

linter: dead code check extended
Added check for null coalesce when the type of the
the first operand is precise and does not contain null.


Patch:
diff --git a/src/linter/block_linter.go b/src/linter/block_linter.go
index 03deebd..b927cc9 100644
--- a/src/linter/block_linter.go
+++ b/src/linter/block_linter.go
@@ -132,7 +132,8 @@ func (b *blockLinter) enterNode(n ir.Node) {
 	case *ir.SpaceshipExpr:
 		b.checkBinaryVoidType(n.Left, n.Right)
 		b.checkBinaryDupArgsNoFloat(n, n.Left, n.Right)
-
+	case *ir.CoalesceExpr:
+		b.checkCoalesceExpr(n)
 	case *ir.TypeCastExpr:
 		if n.Type == "array" {
 			b.checkRedundantCastArray(n.Expr)
@@ -191,6 +192,17 @@ func (b *blockLinter) report(n ir.Node, level int, checkName, msg string, args .
 	b.walker.r.Report(n, level, checkName, msg, args...)
 }
 
+func (b *blockLinter) checkCoalesceExpr(n *ir.CoalesceExpr) {
+	lhsType := solver.ExprType(b.walker.ctx.sc, b.walker.r.ctx.st, n.Left)
+	if !lhsType.IsPrecise() {
+		return
+	}
+
+	if !lhsType.Contains("null") {
+		b.report(n.Right, LevelInformation, "deadCode", "%s is not nullable, right side of the expression is unreachable", irutil.FmtNode(n.Left))
+	}
+}
+
 func (b *blockLinter) checkArrayDimFetch(s *ir.ArrayDimFetchExpr) {
 	typ := solver.ExprType(b.walker.ctx.sc, b.walker.r.ctx.st, s.Variable)
 


Test patch:
diff --git a/src/tests/checkers/deadcode_test.go b/src/tests/checkers/deadcode_test.go
index 63828a8..f90f6e4 100644
--- a/src/tests/checkers/deadcode_test.go
+++ b/src/tests/checkers/deadcode_test.go
@@ -1,1 +1,46 @@
 package checkers_test
+
+import (
+	"testing"
+
+	"github.com/VKCOM/noverify/src/linttest"
+)
+
+func TestDeadCodeInNullCoalesce(t *testing.T) {
+	test := linttest.NewSuite(t)
+	test.AddFile(`<?php
+function retNullable(): ?string {
+	return "";
+}
+
+function f() {
+  $_ = $q ?? 10; // ok
+
+  $a = 100;
+  $_ = $a ?? 10; // dead code
+
+  $c = retNullable();
+  $_ = $c ?? 10; // dead code, but imprecise type
+
+  $d = 10;
+  if (1) {
+    $d = null;
+  }
+  $_ = $d ?? 10; // ok
+
+  $input = [1.5,2,3];
+  $_ = $input["test"] ?? 10; // ok
+  $_ = (int)$input["test"] ?? 10; // dead code
+  $_ = (int)($input["test"] ?? 10); // ok
+
+  global $x;
+  $_ = $x ?? 10; // ok
+}
+
+`)
+	test.Expect = []string{
+		`$a is not nullable, right side of the expression is unreachable`,
+		`(int)$input["test"] is not nullable, right side of the expression is unreachable`,
+	}
+	linttest.RunFilterMatch(test, "deadCode")
+}
diff --git a/src/tests/golden/golden_test.go b/src/tests/golden/golden_test.go
index 7e8151e..4398441 100644
--- a/src/tests/golden/golden_test.go
+++ b/src/tests/golden/golden_test.go
@@ -28,6 +28,9 @@ func TestGolden(t *testing.T) {
 	targets := []*linttest.GoldenTestSuite{
 		{
 			Name: "embeddedrules",
+			Disable: []string{
+				`deadCode`,
+			},
 			Deps: []string{
 				`stubs/phpstorm-stubs/pcre/pcre.php`,
 			},
Repository: VKCOM/noverify
Pull request #946 (2021-03-04 08:15:48)

linter: added a check for casting to the real type and using the is_real function
See [deprecated core real](https://www.php.net/manual/ru/migration74.deprecated.php#migration74.deprecated.core.real).


Patch:
diff --git a/src/linter/block_linter.go b/src/linter/block_linter.go
index 03ae829..a636628 100644
--- a/src/linter/block_linter.go
+++ b/src/linter/block_linter.go
@@ -131,11 +131,7 @@ func (b *blockLinter) enterNode(n ir.Node) {
 	case *ir.CoalesceExpr:
 		b.checkCoalesceExpr(n)
 	case *ir.TypeCastExpr:
-		if n.Type == "array" {
-			b.checkRedundantCastArray(n.Expr)
-		} else {
-			b.checkRedundantCast(n.Expr, n.Type)
-		}
+		b.checkTypeCaseExpr(n)
 
 	case *ir.CloneExpr:
 		b.walker.r.checkKeywordCase(n, "clone")
@@ -187,6 +183,21 @@ func (b *blockLinter) enterNode(n ir.Node) {
 	}
 }
 
+func (b *blockLinter) checkTypeCaseExpr(n *ir.TypeCastExpr) {
+	if n.Type == "array" {
+		b.checkRedundantCastArray(n.Expr)
+	} else {
+		b.checkRedundantCast(n.Expr, n.Type)
+	}
+
+	// We cannot use the value directly, since for real it is equal to float,
+	// so we have to use the token value.
+	if bytes.EqualFold(n.CastTkn.Value, []byte("(real)")) {
+		b.report(n, LevelNotice, "langDeprecated",
+			"use float cast instead of real")
+	}
+}
+
 func (b *blockLinter) report(n ir.Node, level int, checkName, msg string, args ...interface{}) {
 	b.walker.r.Report(n, level, checkName, msg, args...)
 }
@@ -792,6 +803,8 @@ func (b *blockLinter) checkFunctionCall(e *ir.FunctionCallExpr) {
 		}
 		// TODO: handle fprintf as well?
 		b.checkFormatString(e, e.Arg(0))
+	case `\is_real`:
+		b.report(e, LevelNotice, "langDeprecated", "use is_float function instead of is_real")
 	}
 }
 
diff --git a/src/linter/report.go b/src/linter/report.go
index bfac3e3..299a83d 100644
--- a/src/linter/report.go
+++ b/src/linter/report.go
@@ -639,6 +639,17 @@ echo $someVal;`,
 // or
 $_ = 1 ? 2 : (3 ? 4 : 5);`,
 		},
+
+		{
+			Name:     "langDeprecated",
+			Default:  false,
+			Quickfix: true,
+			Comment:  `Report the use of deprecated (per language spec) features.`,
+			Before: `$a = (real)100;
+$_ = is_real($a);`,
+			After: `$a = (float)100;
+$_ = is_float($a);`,
+		},
 	}
 
 	for _, info := range allChecks {


Test patch:
diff --git a/src/tests/checkers/basic_test.go b/src/tests/checkers/basic_test.go
index ec9b0a2..192baa4 100644
--- a/src/tests/checkers/basic_test.go
+++ b/src/tests/checkers/basic_test.go
@@ -2155,3 +2155,22 @@ function f() {
 	}
 	test.RunAndMatch()
 }
+
+func TestRealCastingAndIsRealCall(t *testing.T) {
+	test := linttest.NewSuite(t)
+	test.AddFile(`<?php
+function is_real($a): bool { return true; }
+
+function f() {
+    $a = (real)100;
+    if (is_real($a)) {
+        echo 1;
+    }
+}
+`)
+	test.Expect = []string{
+		`use float cast instead of real`,
+		`use is_float function instead of is_real`,
+	}
+	test.RunAndMatch()
+}
Repository: VKCOM/noverify
Pull request #972 (2021-04-14 08:40:48)

cmd/embeddedrules: add returnAssign checker
Report the use of an assignment in a return statement.

#332

quasilyte: Conflicts.

Patch:
diff --git a/src/cmd/embeddedrules/rules.php b/src/cmd/embeddedrules/rules.php
index 3db6818..0846b86 100644
--- a/src/cmd/embeddedrules/rules.php
+++ b/src/cmd/embeddedrules/rules.php
@@ -458,3 +458,29 @@ function emptyStringCheck() {
         if (!mb_strlen($x)) { $_; }
     }
 }
+
+/**
+ * @comment Report the use of assignment in the return statement.
+ * @before  return $a = 100;
+ * @after   return $a;
+ */
+function returnAssign() {
+    /**
+     * @warning don't use assignment in the return statement
+     */
+    any: {
+        return $_ = $_;
+        return $_ += $_;
+        return $_ -= $_;
+        return $_ *= $_;
+        return $_ /= $_;
+        return $_ %= $_;
+        return $_ &= $_;
+        return $_ |= $_;
+        return $_ ^= $_;
+        return $_ <<= $_;
+        return $_ >>= $_;
+        return $_ .= $_;
+        return $_ ??= $_;
+    }
+}


Test patch:
diff --git a/src/tests/inline/testdata/embeddedrules/returnAssign.php b/src/tests/inline/testdata/embeddedrules/returnAssign.php
new file mode 100644
index 0000000..23d527c
--- /dev/null
+++ b/src/tests/inline/testdata/embeddedrules/returnAssign.php
@@ -0,0 +1,22 @@
+<?php
+
+function returnAssign(): int {
+    $a = 100;
+    echo $a;
+
+    return $a = 1; // want `don't use assignment in the return statement`
+}
+
+function returnAssign2(): int {
+    $a = 100;
+    echo $a;
+
+    return $a += 1; // want `don't use assignment in the return statement`
+}
+
+function returnAssignOk(): int {
+    $a = 100;
+    echo $a;
+
+    return $a; // ok
+}
Repository: VKCOM/noverify
Pull request #979 (2021-04-16 08:50:49)

linter: add check for parent typehint
Checks when type hint `parent` is used in a class that does not have a parent class.

Need to merged after #961.


Patch:
diff --git a/src/linter/report.go b/src/linter/report.go
index 47d6623..6257aab 100644
--- a/src/linter/report.go
+++ b/src/linter/report.go
@@ -653,7 +653,7 @@ $_ = is_float($a);`,
 
 		{
 			Name:     "typeHint",
-			Default:  false,
+			Default:  true,
 			Quickfix: false,
 			Comment:  `Report misuse of type hints.`,
 			Before:   `function f(array $a) {}`,
diff --git a/src/linter/root.go b/src/linter/root.go
index af4be63..4bfc205 100644
--- a/src/linter/root.go
+++ b/src/linter/root.go
@@ -1336,11 +1336,28 @@ func (d *rootWalker) parseTypeNode(n ir.Node) (typ types.Map, ok bool) {
 		return types.Map{}, false
 	}
 
+	d.checkTypeNode(n)
+
 	typeList := typesFromNode(n)
 	tm := newTypesMap(&d.ctx, typeList)
 	return tm, !tm.Empty()
 }
 
+func (d *rootWalker) checkTypeNode(n ir.Node) {
+	if n == nil {
+		return
+	}
+
+	typeList := typesFromNode(n)
+	for _, typ := range typeList {
+		if typ.Elem == "parent" && d.ctx.st.CurrentClass != "" {
+			if d.ctx.st.CurrentParentClass == "" {
+				d.Report(n, LevelError, "typeHint", "cannot use 'parent' typehint when current class has no parent")
+			}
+		}
+	}
+}
+
 // callbackParamByIndex returns the description of the parameter for the function by its index.
 func (d *rootWalker) callbackParamByIndex(param ir.Node, argType types.Map) meta.FuncParam {
 	p := param.(*ir.Parameter)


Test patch:
diff --git a/src/tests/inline/testdata/checkers/typeHint.php b/src/tests/inline/testdata/checkers/typeHint.php
new file mode 100644
index 0000000..991cd7d
--- /dev/null
+++ b/src/tests/inline/testdata/checkers/typeHint.php
@@ -0,0 +1,31 @@
+<?php
+
+class Boo {}
+
+class FooBad {
+    public parent $a; // want `cannot use 'parent' typehint when current class has no parent`
+
+    /** */
+    public function f1(): parent { // want `cannot use 'parent' typehint when current class has no parent`
+        return new Boo;
+    }
+
+    /** */
+    public function f2(parent $a) { // want `cannot use 'parent' typehint when current class has no parent`
+        return new Boo;
+    }
+}
+
+class FooOk extends Boo {
+    public parent $a;
+
+    /** */
+    public function f(): parent {
+        return new Boo;
+    }
+
+    /** */
+    public function f2(parent $a) {
+        return new Boo;
+    }
+}
Repository: VKCOM/noverify
Pull request #996 (2021-06-17 10:37:16)

linter: added check for repeated nullable type
Also fixed a bug where a nullable field in shape
caused the entire shape to become nullable.


Patch:
diff --git a/src/linter/types.go b/src/linter/types.go
index da4e766..d932a57 100644
--- a/src/linter/types.go
+++ b/src/linter/types.go
@@ -23,7 +23,19 @@ func typesFromPHPDoc(ctx *rootContext, typ phpdoc.Type) ([]types.Type, warningSt
 	conv := phpdocTypeConverter{ctx: ctx}
 	result := conv.mapType(typ.Expr)
 	if conv.nullable {
-		result = append(result, types.Type{Elem: "null"})
+		alreadyHasNull := false
+
+		for _, tp := range result {
+			if tp.Elem == "null" {
+				alreadyHasNull = true
+				conv.warning = "repeated nullable doesn't make sense"
+				break
+			}
+		}
+
+		if !alreadyHasNull {
+			result = append(result, types.Type{Elem: "null"})
+		}
 	}
 	return result, conv.warning
 }
@@ -167,6 +179,7 @@ func (conv *phpdocTypeConverter) mapShapeType(params []phpdoc.TypeExpr) []types.
 				Elem: "null",
 				Dims: 0,
 			})
+			conv.nullable = false
 		}
 
 		props = append(props, autogen.ShapeTypeProp{


Test patch:
diff --git a/src/tests/checkers/phpdoc_test.go b/src/tests/checkers/phpdoc_test.go
index 81900d9..8531a43 100644
--- a/src/tests/checkers/phpdoc_test.go
+++ b/src/tests/checkers/phpdoc_test.go
@@ -478,3 +478,30 @@ function f2($a) {
 	}
 	test.RunAndMatch()
 }
+
+func TestPhpdocTwiceNullableTypes(t *testing.T) {
+	test := linttest.NewSuite(t)
+	test.AddFile(`<?php
+/**
+ * @param ?int|null $a
+ * @param ?string[]|null $b
+ * @param ?Foo|null $c
+ * @param shape(name: ?string, age: int)|null $d
+ * @return ?int|null
+ */
+function f($a, $b, $c, $d) {
+  echo $a;
+  echo $b;
+  echo $c;
+  echo $d;
+  return null;
+}
+`)
+	test.Expect = []string{
+		`repeated nullable doesn't make sense`,
+		`repeated nullable doesn't make sense`,
+		`repeated nullable doesn't make sense`,
+		`repeated nullable doesn't make sense`,
+	}
+	test.RunAndMatch()
+}
Repository: VKCOM/noverify
Pull request #1040 (2021-07-07 03:03:17)

solver: now typecast to an array does not overwrite existing types
For example:

```php
/**
 * @return Foo[]
 */
function f() {
  return [];
}

function f1() {
  $a = (array) f();
  echo $a[0]->f();
}
```

### Before:
```
<critical> ERROR   undefined: Call to undefined method {mixed}->f() at ...:16
  echo $a[0]->f();
              ^
```

### After:

No error.


Patch:
diff --git a/src/solver/exprtype.go b/src/solver/exprtype.go
index 728dba8..3d4473f 100644
--- a/src/solver/exprtype.go
+++ b/src/solver/exprtype.go
@@ -124,7 +124,7 @@ func exprTypeLocalCustom(sc *meta.Scope, cs *meta.ClassParseState, n ir.Node, cu
 	case *ir.PreDecExpr:
 		return unaryMathOpType(sc, cs, n.Variable, custom)
 	case *ir.TypeCastExpr:
-		return typeCastType(n)
+		return typeCastType(sc, cs, n, custom)
 	case *ir.ShiftLeftExpr, *ir.ShiftRightExpr:
 		return types.PreciseIntType
 	case *ir.ClassConstFetchExpr:
@@ -378,10 +378,11 @@ func classConstFetchType(n *ir.ClassConstFetchExpr, cs *meta.ClassParseState) ty
 	return types.NewMap(types.WrapClassConstFetch(className, n.ConstantName.Value))
 }
 
-func typeCastType(n *ir.TypeCastExpr) types.Map {
+func typeCastType(sc *meta.Scope, cs *meta.ClassParseState, n *ir.TypeCastExpr, custom []CustomType) types.Map {
 	switch n.Type {
 	case "array":
-		return types.NewMap("mixed[]")
+		typ := exprTypeLocalCustom(sc, cs, n.Expr, custom)
+		return typ.Append(types.NewMap("mixed[]"))
 	case "int":
 		return types.PreciseIntType
 	case "string":


Test patch:
diff --git a/src/tests/exprtype/exprtype_test.go b/src/tests/exprtype/exprtype_test.go
index 304dbf7..6620537 100644
--- a/src/tests/exprtype/exprtype_test.go
+++ b/src/tests/exprtype/exprtype_test.go
@@ -3046,6 +3046,32 @@ function f($arr, $arr1, $arr2, $arr3, $arr4, $arr5) {
 	runExprTypeTest(t, &exprTypeTestParams{code: code})
 }
 
+func TestArrayTypeCast(t *testing.T) {
+	code := `<?php
+class Foo {}
+
+/**
+ * @return Foo[]
+ */
+function f() {
+  return [];
+}
+
+function f1() {
+  $a = (array) f();
+  exprtype($a, "\Foo[]|mixed[]");
+  exprtype($a[0], "\Foo|mixed");
+
+  $b = (array) 10;
+  exprtype($b, "int|mixed[]");
+
+  $c = (array) [1, "s"];
+  exprtype($c, "mixed[]");
+}
+`
+	runExprTypeTest(t, &exprTypeTestParams{code: code})
+}
+
 func runExprTypeTest(t *testing.T, params *exprTypeTestParams) {
 	exprTypeTestImpl(t, params, false)
 }
Repository: VKCOM/noverify
Pull request #1049 (2021-07-08 13:34:13)

linter: fixed check for class name case


Patch:
diff --git a/src/linter/block_linter.go b/src/linter/block_linter.go
index e3ac0c3..23082c3 100644
--- a/src/linter/block_linter.go
+++ b/src/linter/block_linter.go
@@ -1140,7 +1140,7 @@ func (b *blockLinter) checkClassConstFetch(e *ir.ClassConstFetchExpr) {
 	if !utils.IsSpecialClassName(e.Class) {
 		usedClassName, ok := solver.GetClassName(b.classParseState(), e.Class)
 		if ok {
-			b.walker.r.checkNameCase(e.Class, usedClassName, fetch.implClassName)
+			b.walker.r.checkNameCase(e.Class, usedClassName, fetch.className)
 		}
 	}
 
diff --git a/src/linter/utils.go b/src/linter/utils.go
index 0740a2d..29e44da 100644
--- a/src/linter/utils.go
+++ b/src/linter/utils.go
@@ -489,6 +489,11 @@ func resolveClassConstFetch(st *meta.ClassParseState, e *ir.ClassConstFetchExpr)
 		return classPropertyFetchInfo{canAnalyze: false}
 	}
 
+	class, ok := st.Info.GetClass(className)
+	if ok {
+		className = class.Name
+	}
+
 	info, implClass, found := solver.FindConstant(st.Info, className, constName.Value)
 
 	return classPropertyFetchInfo{


Test patch:
diff --git a/src/tests/checkers/basic_test.go b/src/tests/checkers/basic_test.go
index b55110d..ab6e513 100644
--- a/src/tests/checkers/basic_test.go
+++ b/src/tests/checkers/basic_test.go
@@ -1859,12 +1859,18 @@ func TestSwitchBreak(t *testing.T) {
 func TestNameCase(t *testing.T) {
 	test := linttest.NewSuite(t)
 	test.AddFile(`<?php
+interface BarAble {
+  const TheConst4 = 10;
+}
+
 class Bar {
   const TheConst2 = 10;
+  const TheConst3 = 10;
 }
 
-class FooBar extends Bar {
+class FooBar extends Bar implements BarAble {
   const TheConst = 10;
+  const TheConst3 = 10;
 
   public function method_a() {
     echo self::TheConst;
@@ -1872,6 +1878,8 @@ class FooBar extends Bar {
     echo parent::TheConst2;
 
     $_ = FOObar::TheConst;
+    $_ = FOObar::TheConst3;
+    $_ = FOObar::TheConst4;
   }
 }
 
@@ -1893,6 +1901,8 @@ $_ = FOObar::TheConst;
 		`\func_A should be spelled \func_a`,
 		`\FOObar should be spelled \FooBar`,
 		`\FOObar should be spelled \FooBar`,
+		`\FOObar should be spelled \FooBar`,
+		`\FOObar should be spelled \FooBar`,
 	}
 	linttest.RunFilterMatch(test, `nameMismatch`)
 }
Repository: VKCOM/noverify
Pull request #1054 (2021-07-12 19:13:11)

embeddedrules: fixed autofix for `langDeprecated.define` rule


Patch:
diff --git a/src/cmd/embeddedrules/rules.php b/src/cmd/embeddedrules/rules.php
index da81d1e..e1e8c65 100644
--- a/src/cmd/embeddedrules/rules.php
+++ b/src/cmd/embeddedrules/rules.php
@@ -432,9 +432,9 @@ function langDeprecated() {
 
     /**
      * @warning Define defaults to a case sensitive constant, the third argument can be removed
-     * @fix     define($_, $_);
+     * @fix     define($x, $y)
      */
-    define($_, $_, false);
+    define($x, $y, false);
 }
 
 /**


Test patch:
diff --git a/src/tests/golden/testdata/quickfix/langDeprecated.php b/src/tests/golden/testdata/quickfix/langDeprecated.php
new file mode 100644
index 0000000..cfd4bbf
--- /dev/null
+++ b/src/tests/golden/testdata/quickfix/langDeprecated.php
@@ -0,0 +1,5 @@
+<?php
+
+define("CONST", 10, false);
+define("CONST1", 10, true);
+define("CONST2", 10);
diff --git a/src/tests/golden/testdata/quickfix/langDeprecated.php.fix.expected b/src/tests/golden/testdata/quickfix/langDeprecated.php.fix.expected
new file mode 100644
index 0000000..0c2a752
--- /dev/null
+++ b/src/tests/golden/testdata/quickfix/langDeprecated.php.fix.expected
@@ -0,0 +1,5 @@
+<?php
+
+define("CONST", 10);
+define("CONST1", 10, true);
+define("CONST2", 10);
Repository: VKCOM/noverify
Pull request #1057 (2021-07-13 21:30:25)

irconv: fixed PHPDoc for class with modifiers
Previously, if a class had modifiers, then PHPDoc was not attached to the class.


Patch:
diff --git a/src/ir/irconv/irconv.go b/src/ir/irconv/irconv.go
index a0616ee..8986f15 100644
--- a/src/ir/irconv/irconv.go
+++ b/src/ir/irconv/irconv.go
@@ -2220,7 +2220,13 @@ func (c *Converter) convClass(n *ast.StmtClass) ir.Node {
 		}
 	}
 
-	class.Doc = c.getPhpDoc(n.ClassTkn)
+	// If there are modifiers, then PHPDoc will be bound to the
+	// first one, otherwise to the class token.
+	if len(n.Modifiers) != 0 {
+		class.Doc = c.getPhpDoc(ir.GetFirstToken(c.convNode(n.Modifiers[0])))
+	} else {
+		class.Doc = c.getPhpDoc(n.ClassTkn)
+	}
 
 	if n.Name == nil {
 		// Anonymous class expression.


Test patch:
diff --git a/src/tests/checkers/oop_test.go b/src/tests/checkers/oop_test.go
index 77c2172..49c9834 100644
--- a/src/tests/checkers/oop_test.go
+++ b/src/tests/checkers/oop_test.go
@@ -294,6 +294,64 @@ $_ = WithProps::$int;
 	test.RunAndMatch()
 }
 
+func TestPhpdocPropertyForClassWithModifiers(t *testing.T) {
+	test := linttest.NewSuite(t)
+	test.AddFile(`<?php
+/**
+ * @property int $int
+ * @property string $string - optional description.
+ * @property-read string $name Name of the class
+ */
+abstract class WithPropsBase {
+  /***/
+  public function getInt() {
+    return $this->int;
+  }
+  /***/
+  public function getString() {
+    return $this->string;
+  }
+}
+
+/**
+ * @property int $int1
+ * @property string $string1 - optional description.
+ * @property-read string $name1 Name of the class
+ */
+final class WithProps extends WithPropsBase {}
+
+$_ = (new WithProps())->int;
+$_ = (new WithProps())->string;
+
+function f(WithProps $x) {
+  return $x->int + $x->int1;
+}
+
+/**
+ * @param WithProps|null $y
+ */
+function f2($y = null) {
+  echo $y->name;
+  echo $y->string1;
+}
+
+// Can't access them as static props/constants.
+$_ = WithProps::int;
+$_ = WithProps::int1;
+$_ = WithProps::$int;
+$_ = WithProps::$int1;
+`)
+
+	test.Expect = []string{
+		`Class constant \WithProps::int does not exist`,
+		`Class constant \WithProps::int1 does not exist`,
+		`Property \WithProps::$int does not exist`,
+		`Property \WithProps::$int1 does not exist`,
+	}
+
+	test.RunAndMatch()
+}
+
 func TestInheritDoc(t *testing.T) {
 	test := linttest.NewSuite(t)
 	test.AddFile(`<?php