,error_levels?:string[]}> */ public function providerValidCodeParse() { return [ 'immutableClassGenerating' => [ 'a = $a; $this->b = $b; } public function setA(int $a) : self { return new self($a, $this->b); } }', ], 'callInternalClassMethod' => [ 'a = $a; } public function getA() : string { return $this->a; } public function getHelloA() : string { return "hello" . $this->getA(); } }', ], 'addToCart' => [ 'items = $items; } public function addItem(CartItem $item) : self { $items = $this->items; $items[] = $item; return new Cart($items); } } /** @psalm-immutable */ class CartItem { public string $name; public float $price; public function __construct(string $name, float $price) { $this->name = $name; $this->price = $price; } } /** @psalm-pure */ function addItemToCart(Cart $c, string $name, float $price) : Cart { return $c->addItem(new CartItem($name, $price)); }', ], 'allowImpureStaticMethod' => [ 'id = $id; } public static function fromString(string $id): self { return new self($id . rand(0, 1)); } }' ], 'allowPropertySetOnNewInstance' => [ 'bar = $bar; } public function withBar(string $bar): self { $new = new Foo("hello"); $new->bar = $bar; return $new; } }' ], 'allowArrayMapCallable' => [ 'line1 = $line1; $this->line2 = $line2; $this->city = $city; } public function __toString() { $parts = [ $this->line1, $this->line2 ?? "", $this->city, ]; // Remove empty parts $parts = \array_map("trim", $parts); $parts = \array_filter($parts, "strlen"); $parts = \array_map(function(string $s) { return $s;}, $parts); return \implode(", ", $parts); } }' ], 'allowPropertyAssignmentInUnserialize' => [ 'data = "Foo"; } public function serialize() { return $this->data; } public function unserialize($data) { $this->data = $data; } public function getData(): string { return $this->data; } }' ], 'allowMethodOverriding' => [ 'a = $a; } public function getA() : string { return $this->a; } } /** @method string getA() */ class B extends A {}', ], 'immutableClassWithCloneAndPropertyChange' => [ 'bar = $bar; } public function withBar(string $bar): self { $new = clone $this; $new->bar = $bar; return $new; } }', ], 'immutableClassWithCloneAndPropertyAppend' => [ 'bar = $bar; } public function withBar(string $bar): self { $new = clone $this; $new->bar .= $bar; return $new; } }', ], 'memoizeImmutableCalls' => [ 'error = $error; } public function getError(): ?string { return $this->error; } } $dto = new DTO("BOOM!"); if ($dto->getError()) { takesString($dto->getError()); }' ], 'allowConstructorPrivateUnusedMethods' => [ 'test(); $this->commission = 1; } private function test(): void {} }' ], ]; } /** * @return iterable */ public function providerInvalidCodeParse() { return [ 'immutablePropertyAssignmentInternally' => [ 'a = $a; $this->b = $b; } public function setA(int $a): void { $this->a = $a; } }', 'error_message' => 'InaccessibleProperty', ], 'immutablePropertyAssignmentExternally' => [ 'a = $a; $this->b = $b; } } $a = new A(4, "hello"); $a->b = "goodbye";', 'error_message' => 'InaccessibleProperty', ], 'callImpureFunction' => [ 'a = $a; $this->b = $b; } public function bar() : void { header("Location: https://vimeo.com"); } }', 'error_message' => 'ImpureFunctionCall', ], 'callExternalClassMethod' => [ 'a = $a; } public function getA() : string { return $this->a; } public function redirectToA() : void { B::setRedirectHeader($this->getA()); } } class B { public static function setRedirectHeader(string $s) : void { header("Location: $s"); } }', 'error_message' => 'ImpureMethodCall', ], 'cloneMutatingClass' => [ 'bar = $bar; } public function withBar(Bar $b): Bar { $new = clone $b; $b->a = $this->bar; return $new; } } class Bar { public string $a = "hello"; }', 'error_message' => 'ImpurePropertyAssignment', ], 'mustBeImmutableLikeInterfaces' => [ 'counter; } }', 'error_message' => 'MissingImmutableAnnotation', ], 'inheritImmutabilityFromParent' => [ 'counter; } }', 'error_message' => 'MissingImmutableAnnotation', ], ]; } }