<commit_before><?php
namespace Psalm\Tests;

use const DIRECTORY_SEPARATOR;

class PureAnnotationTest extends TestCase
{
    use Traits\InvalidCodeAnalysisTestTrait;
    use Traits\ValidCodeAnalysisTestTrait;

    /**
     * @return iterable<string,array{string,assertions?:array<string,string>,error_levels?:string[]}>
     */
    public function providerValidCodeParse()
    {
        return [
            'simplePureFunction' => [
                '<?php
                    namespace Bar;

                    class A {
                        public int $a = 5;
                    }

                    /** @psalm-pure */
                    function filterOdd(int $i, A $a) : ?int {
                        if ($i % 2 === 0 || $a->a === 2) {
                            return $i;
                        }

                        $a = new A();

                        return null;
                    }',
            ],
            'pureFunctionCallingBuiltinFunctions' => [
                '<?php
                    namespace Bar;

                    /** @psalm-pure */
                    function lower(string $s) : string {
                        return substr(strtolower($s), 0, 10);
                    }',
            ],
            'pureWithStrReplace' => [
                '<?php
                    /** @psalm-pure */
                    function highlight(string $needle, string $output) : string {
                        $needle = preg_quote($needle, \'#\');
                        $needles = str_replace([\'"\', \' \'], [\'\', \'|\'], $needle);
                        $output = preg_replace("#({$needles})#im", "<mark>$1</mark>", $output);

                        return $output;
                    }'
            ],
            'implicitAnnotations' => [
                '<?php
                    abstract class Foo {
                        private array $options;
                        private array $defaultOptions;

                        function __construct(array $options) {
                            $this->setOptions($options);
                            $this->setDefaultOptions($this->getOptions());
                        }

                        function getOptions(): array {
                            return $this->options;
                        }

                        function setOptions(array $options): void {
                            $this->options = $options;
                        }

                        function setDefaultOptions(array $defaultOptions): void {
                            $this->defaultOptions = $defaultOptions;
                        }
                    }',
            ],
            'canCreateObjectWithNoExternalMutations' => [
                '<?php
                    /** @psalm-external-mutation-free */
                    class Counter {
                        private int $count = 0;

                        public function __construct(int $count) {
                            $this->count = $count;
                        }

                        public function increment() : void {
                            $this->count++;
                        }
                    }

                    /** @psalm-pure */
                    function makesACounter(int $i) : Counter {
                        $c = new Counter($i);
                        $c->increment();
                        return $c;
                    }',
            ],
            'canCreateImmutableObject' => [
                '<?php
                    /** @psalm-immutable */
                    class A {
                        private string $s;

                        public function __construct(string $s) {
                            $this->s = $s;
                        }

                        public function getShort() : string {
                            return substr($this->s, 0, 5);
                        }
                    }

                    /** @psalm-pure */
                    function makeA(string $s) : A {
                        $a = new A($s);

                        if ($a->getShort() === "bar") {
                            return new A("foo");
                        }
<commit_msg>Add more test cases for @psalm-external-mutation-free<commit_after><?php
namespace Psalm\Tests;

use const DIRECTORY_SEPARATOR;

class PureAnnotationTest extends TestCase
{
    use Traits\InvalidCodeAnalysisTestTrait;
    use Traits\ValidCodeAnalysisTestTrait;

    /**
     * @return iterable<string,array{string,assertions?:array<string,string>,error_levels?:string[]}>
     */
    public function providerValidCodeParse()
    {
        return [
            'simplePureFunction' => [
                '<?php
                    namespace Bar;

                    class A {
                        public int $a = 5;
                    }

                    /** @psalm-pure */
                    function filterOdd(int $i, A $a) : ?int {
                        if ($i % 2 === 0 || $a->a === 2) {
                            return $i;
                        }

                        $a = new A();

                        return null;
                    }',
            ],
            'pureFunctionCallingBuiltinFunctions' => [
                '<?php
                    namespace Bar;

                    /** @psalm-pure */
                    function lower(string $s) : string {
                        return substr(strtolower($s), 0, 10);
                    }',
            ],
            'pureWithStrReplace' => [
                '<?php
                    /** @psalm-pure */
                    function highlight(string $needle, string $output) : string {
                        $needle = preg_quote($needle, \'#\');
                        $needles = str_replace([\'"\', \' \'], [\'\', \'|\'], $needle);
                        $output = preg_replace("#({$needles})#im", "<mark>$1</mark>", $output);

                        return $output;
                    }'
            ],
            'implicitAnnotations' => [
                '<?php
                    abstract class Foo {
                        private array $options;
                        private array $defaultOptions;

                        function __construct(array $options) {
                            $this->setOptions($options);
                            $this->setDefaultOptions($this->getOptions());
                        }

                        function getOptions(): array {
                            return $this->options;
                        }

                        function setOptions(array $options): void {
                            $this->options = $options;
                        }

                        function setDefaultOptions(array $defaultOptions): void {
                            $this->defaultOptions = $defaultOptions;
                        }
                    }',
            ],
            'canCreateObjectWithNoExternalMutations' => [
                '<?php
                    /** @psalm-external-mutation-free */
                    class Counter {
                        private int $count = 0;

                        public function __construct(int $count) {
                            $this->count = $count;
                        }

                        public function increment() : void {
                            $this->count++;
                        }

                        public function incrementByTwo() : void {
                            $this->count = $this->count + 2;
                        }

                        public function incrementByFive() : void {
                            $this->count += 5;
                        }
                    }

                    /** @psalm-pure */
                    function makesACounter(int $i) : Counter {
                        $c = new Counter($i);
                        $c->increment();
                        $c->incrementByTwo();
                        $c->incrementByFive();
                        return $c;
                    }',
            ],
            'canCreateImmutableObject' => [
                '<?php
                    /** @psalm-immutable */
                    class A {
                        private string $s;

                        public function __construct(string $s) {
                            $this->s = $s;
                        }

                        public function getShort() : string {
                            return substr($this->s, 0, 5);
                        }
                    }

                    /** @psalm-pure */
                    function makeA(string $s) : A {
                        $a = new A($s);

                        if ($a->getShort() === "bar") {
                            return new A("foo");
                        }

{'fraction_non_alphanumeric': 0.10801982428474882, 'fraction_numerical': 0.0027033115566569045, 'mean_word_length': 0.8224548440065681, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 54, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3282456', 'n_tokens_mistral': 2107, 'n_tokens_neox': 1853, 'n_words': 386}