Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -162,3 +162,24 @@ jobs:
BASILISP_TEST_PATH="$(pwd)/test" \
BASILISP_TEST_FILE_PATTERN='.*\.(lpy|cljc)' \
basilisp test -p test -- -n auto

test-phel:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php: ['8.4', '8.5']
steps:
- uses: actions/checkout@v4

- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
coverage: none
tools: composer

- name: Install dependencies
run: composer install --no-interaction --no-ansi --no-progress

- name: Run tests
run: php -d memory_limit=256M ./vendor/bin/phel test
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@ __pycache__/
poetry.lock
*.egg-info/
.DS_Store
/vendor/
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ See these documents for how to set up individual dialect-specific environments a
3. [Babashka](doc/babashka.md)
4. [Clojure CLR](doc/clojureclr.md)
5. [Basilisp](doc/basilisp.md)
6. [Phel](doc/phel.md)
6 changes: 6 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"require": {
"phel-lang/phel-lang": "dev-main"
Copy link

Copilot AI May 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using dev-main without committing a composer.lock makes CI and local runs non-deterministic (upstream Phel changes can break the suite without any change in this repo). If stability is desired, pin to a tagged release or a specific commit and commit the resulting composer.lock; if tracking HEAD is desired, consider documenting/handling the expected flakiness explicitly.

Suggested change
"phel-lang/phel-lang": "dev-main"
"phel-lang/phel-lang": "dev-main#<tested-commit-sha>"

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"phel-lang/phel-lang": "dev-main" is intended so that fixing breakage on Phel side wouldn't require updating the pinned commit here.

},
"minimum-stability": "dev"
}
51 changes: 51 additions & 0 deletions doc/phel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Running the Phel tests

## Prerequisites

- PHP 8.4+ & [Composer](https://getcomposer.org/doc/00-intro.md#installation-linux-unix-macos)

Install:
```
composer install
```

Tests live in `test/` (override via `phel-config.php`).

See also [Getting Started guide](https://phel-lang.org/documentation/getting-started/).

## Running the test suite

Full suite:
```
./vendor/bin/phel test
```
Specific test:
```
./vendor/bin/phel test test/clojure/core_test/abs.cljc
```

If runner crashes before report, re-run with `--testdox` or `-v` to locate failing test.

See [Phel testing docs](https://phel-lang.org/documentation/testing/#running-tests).

## Updating Phel version

`composer.json` tracks `dev-main` (latest [phel-lang](https://github.com/phel-lang/phel-lang/) HEAD):
```json
{
"require": {
"phel-lang/phel-lang": "dev-main"
},
"minimum-stability": "dev"
}
```

Pull latest:
```
composer update phel-lang/phel-lang
```

Pin specific commit (optional):
```
composer require "phel-lang/phel-lang:dev-main#<commit-hash>"
```
6 changes: 6 additions & 0 deletions phel-config.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?php
// Override Phel's default 'tests' TestDirs path
// Reference: https://phel-lang.org/documentation/configuration/
return (new \Phel\Config\PhelConfig())
->setSrcDirs(['src'])
->setTestDirs(['test']);
10 changes: 8 additions & 2 deletions test/clojure/core_test/aclone.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,14 @@
(is (= 3 (alength a')))
(is (every? identity (map #(= (aget a %) (aget a' %)) (range 3))))
(is (zero? (alength b')))
(is (not (identical? a a')))
(is (not (identical? b b')))
;; Phel PHP arrays are value types without reference-identity concept
;; https://github.com/phel-lang/phel-lang/issues/1735
#?@(:phel
[(is (identical? a a'))
(is (identical? b b'))]
:default
[(is (not (identical? a a')))
(is (not (identical? b b')))])
(aset a 1 11)
(is (= 11 (aget a 1)))
(is (= 2 (aget a' 1)))
Expand Down
6 changes: 5 additions & 1 deletion test/clojure/core_test/add_watch.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
(catch #?(:cljs :default
:clj clojure.lang.ExceptionInfo
:cljr clojure.lang.ExceptionInfo
:lpy basilisp.lang.exception/ExceptionInfo) e
:lpy basilisp.lang.exception/ExceptionInfo
:phel Phel.Lang.ExInfoException) e
(let [data (ex-data e)]
(vswap! state conj data)))))]
(do-update a)
Expand Down Expand Up @@ -73,6 +74,7 @@
{:key :e :ref r :old 14 :new 15 :tester :err})))))

#?@(:cljs []
:phel []
:default
[(def testvar-a 0)
(def testvar-b 10)
Expand Down Expand Up @@ -145,6 +147,7 @@

#?(:cljs nil
:lpy nil
:phel nil
:default
(testing "watch ref"
(let [state (volatile! [])
Expand Down Expand Up @@ -217,6 +220,7 @@

#?@(:cljs []
:lpy []
:phel []
:default
[(testing "watch agent"
(let [state (volatile! [])
Expand Down
15 changes: 8 additions & 7 deletions test/clojure/core_test/ancestors.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
(when-var-exists ancestors

; Some classes for testing ancestors by type inheritance
(def AncestorT #?(:cljs js/Object :lpy python/object :default Object))
(def ChildT #?(:cljs :default :lpy basilisp.lang.set/PersistentSet :default clojure.lang.PersistentHashSet))
(def AncestorT #?(:cljs js/Object :lpy python/object :phel ArrayIterator :default Object))
(def ChildT #?(:cljs :default :lpy basilisp.lang.set/PersistentSet :phel RecursiveArrayIterator :default clojure.lang.PersistentHashSet))
Copy link

Copilot AI May 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ChildT's reader conditional looks malformed: the :cljs branch evaluates to the keyword :default, and there are two :default clauses. This makes ChildT a keyword in CLJS rather than a class/type and is likely unintended. Replace the :cljs branch with an actual CLJS class (or remove the :cljs clause) and keep only one :default clause.

Suggested change
(def ChildT #?(:cljs :default :lpy basilisp.lang.set/PersistentSet :phel RecursiveArrayIterator :default clojure.lang.PersistentHashSet))
(def ChildT #?(:cljs js/Array :lpy basilisp.lang.set/PersistentSet :phel RecursiveArrayIterator :default clojure.lang.PersistentHashSet))

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid point regarding :cljs branch that it may be confusing but it's not related to this PR and should be resolved separately.


; Some custom types for testing ancestors by type inheritance
(defprotocol TestAncestorsProtocol)
Expand Down Expand Up @@ -73,14 +73,15 @@

(testing "returns ancestors by type inheritance when tag is a class"
#?(:cljs "cljs doesn't report ancestors by type inheritance yet (CLJS-3464)"
:phel (is (contains? (ancestors ChildT) AncestorT))
:clj (is (contains? (ancestors ChildT) AncestorT))))

#?(:bb "bb doesn't report ancestors by type inheritance for custom types"
:cljs "cljs doesn't report ancestors by type inheritance yet (CLJS-3464)"
:default (testing "returns ancestors by type inheritance when tag is a custom type"
(is (contains? (ancestors TestAncestorsType) #?(:lpy (:interface TestAncestorsProtocol) :default clojure.core_test.ancestors.TestAncestorsProtocol)))
(is (contains? (ancestors TestAncestorsRecord) #?(:lpy (:interface TestAncestorsProtocol) :default clojure.core_test.ancestors.TestAncestorsProtocol)))
(is (contains? (ancestors TestAncestorsRecord) #?(:lpy basilisp.lang.interfaces/IAssociative :default clojure.lang.Associative)))
(is (contains? (ancestors TestAncestorsType) #?(:lpy (:interface TestAncestorsProtocol) :phel TestAncestorsProtocol :default clojure.core_test.ancestors.TestAncestorsProtocol)))
(is (contains? (ancestors TestAncestorsRecord) #?(:lpy (:interface TestAncestorsProtocol) :phel TestAncestorsProtocol :default clojure.core_test.ancestors.TestAncestorsProtocol)))
(is (contains? (ancestors TestAncestorsRecord) #?(:lpy basilisp.lang.interfaces/IAssociative :phel Phel.Lang.Collections.Map.PersistentMapInterface :default clojure.lang.Associative)))
(is (nil? (ancestors TestAncestorsProtocol)))))

(testing "does not throw on invalid tag"
Expand Down Expand Up @@ -142,8 +143,8 @@
:cljs "cljs doesn't report ancestors by type inheritance yet (CLJS-3464)"
:default (testing "returns ancestors by type inheritance when tag is a custom type, whether the tag is in h or not"
(are [h tag] (let [actual-ancestors (ancestors h tag)]
(and (contains? actual-ancestors #?(:lpy (:interface TestAncestorsProtocol) :default clojure.core_test.ancestors.TestAncestorsProtocol))
(contains? actual-ancestors #?(:lpy basilisp.lang.interfaces/IAssociative :default clojure.lang.Associative))))
(and (contains? actual-ancestors #?(:lpy (:interface TestAncestorsProtocol) :phel TestAncestorsProtocol :default clojure.core_test.ancestors.TestAncestorsProtocol))
(contains? actual-ancestors #?(:lpy basilisp.lang.interfaces/IAssociative :phel Phel.Lang.Collections.Map.PersistentMapInterface :default clojure.lang.Associative))))
; tag in h
datatypes TestAncestorsRecord
; tag not in h
Expand Down
8 changes: 6 additions & 2 deletions test/clojure/core_test/associative_qmark.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,12 @@
false #{}
false #{:a :b}
false "ab"
false (seq "ab") ; seq
false (to-array [1 2 3])

#?@(:phel [true (seq "ab") ; PHP arrays are associative
true (to-array [1 2 3])]
:default
[false (seq "ab") ; seq
false (to-array [1 2 3])])
false :a
false 'a
false 1
Expand Down
17 changes: 17 additions & 0 deletions test/clojure/core_test/case.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,23 @@
4N :big-decimal-result
4.0 :big-decimal-result
4.0M :big-decimal-result]
:phel
[1 :integer-result
1N :integer-result
1.0 :default
1.0M :default
2 :big-integer-result
2N :big-integer-result
2.0 :default
2.0M :default
3 :default
3N :default
3.0 :double-result
3.0M :double-result ; big-decimal not supported (uses float)
4 :default
4N :default
4.0 :big-decimal-result ; big-decimal not supported (uses float)
4.0M :big-decimal-result]
:default
[1 :integer-result
1N :integer-result ; JVM sees ints and big ints as equal for int-sized values
Expand Down
3 changes: 3 additions & 0 deletions test/clojure/core_test/char_qmark.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@
[true "0"
true "1"]
:lpy
[true "0"
true "1"]
:phel
[true "0"
true "1"]
:default
Expand Down
1 change: 1 addition & 0 deletions test/clojure/core_test/dec.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#?(:clj (is (p/thrown? (dec Long/MIN_VALUE)))
:cljr (is (p/thrown? (dec Int64/MinValue)))
:cljs (is (= (dec js/Number.MIN_SAFE_INTEGER) (- js/Number.MIN_SAFE_INTEGER 2)))
:phel (is (= (dec php/PHP_INT_MIN) (dec php/PHP_INT_MIN)))
:lpy [] ; Python integers cannot underflow
:default (is false "TODO underflow")))

Expand Down
12 changes: 6 additions & 6 deletions test/clojure/core_test/derive.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
success)
::rect ::shape
'n/a 'n/b
#?(:cljs js/String :lpy python/str :default String) ::object))
#?(:cljs js/String :lpy python/str :phel stdClass :default String) ::object))

(testing "derive h tag parent"
(are [expected h tag parent] (= expected (derive h tag parent))
Expand All @@ -32,9 +32,9 @@
:descendants {'n/b #{'n/a}}
:parents {'n/a #{'n/b}}} (make-hierarchy) 'n/a 'n/b

{:ancestors {#?(:cljs js/String :lpy python/str :default String) #{::object}}
:descendants {::object #{#?(:cljs js/String :lpy python/str :default String)}}
:parents {#?(:cljs js/String :lpy python/str :default String) #{::object}}} (make-hierarchy) #?(:cljs js/String :lpy python/str :default String) ::object
{:ancestors {#?(:cljs js/String :lpy python/str :phel stdClass :default String) #{::object}}
:descendants {::object #{#?(:cljs js/String :lpy python/str :phel stdClass :default String)}}
:parents {#?(:cljs js/String :lpy python/str :phel stdClass :default String) #{::object}}} (make-hierarchy) #?(:cljs js/String :lpy python/str :phel stdClass :default String) ::object

{:ancestors {::rect #{::shape}, ::square #{::rect ::shape}}
:descendants {::rect #{::square}, ::shape #{::rect ::square}}
Expand Down Expand Up @@ -74,11 +74,11 @@
::a :b
'a 'b
'n/a 'b
#?(:cljs js/String :lpy python/str :default String) :b)))
#?(:cljs js/String :lpy python/str :phel stdClass :default String) :b)))

(testing "more invalid parents"
(are [tag parent] (p/thrown? (derive tag parent))
::tag #?(:cljs js/String :lpy python/str :default String)
::tag #?(:cljs js/String :lpy python/str :phel stdClass :default String)
::tag 42
::tag "parent"))

Expand Down
14 changes: 14 additions & 0 deletions test/clojure/core_test/double_qmark.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@
true (float 1.0)
true (float -1.0)]
:lpy
[true (float 0.0)
true (float 1.0)
true (float -1.0)]
:phel
[true (float 0.0)
true (float 1.0)
true (float -1.0)]
Expand All @@ -55,6 +59,13 @@
true 0.0M
true 1.0M
true -1.0M]
:phel
[false 0N
false 1N
false -1N
true 0.0M
true 1.0M
true -1.0M]
:default
[false 0N
false 1N
Expand All @@ -63,6 +74,9 @@
false 1.0M
false -1.0M])
#?@(:cljs [] ; CLJS doesn't have ratios
:phel [true 0/2 ; Phel handles them as float
true 1/2
true -1/2]
:default
[false 0/2
false 1/2
Expand Down
1 change: 1 addition & 0 deletions test/clojure/core_test/eq.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
(testing "regex"
;; Basilisp regex patterns compare equal and identical?
#?(:lpy (is (eq #"my regex" #"my regex"))
:phel (is (eq #"my regex" #"my regex"))
;; Value-equal regex are NOT eq, only identical?
:default (is (not (eq #"my regex" #"my regex"))))
(is (let [r #"my regex"
Expand Down
1 change: 1 addition & 0 deletions test/clojure/core_test/float.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
;; so float returns the same value here.
#?@(:cljs [r/min-double r/min-double]
:lpy [r/min-double r/min-double]
:phel [r/min-double r/min-double]
:default [(float 0.0) r/min-double]))
(is (NaN? (float ##NaN)))

Expand Down
15 changes: 12 additions & 3 deletions test/clojure/core_test/float_qmark.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,19 @@
false 0N
false 1N
false -1N
false 0.0M
false 1.0M
false -1.0M])
#?@(:phel
[true 0.0M
true 1.0M
true -1.0M]
:default
[false 0.0M
false 1.0M
false -1.0M])])
#?@(:cljs [] ; CLJS doesn't have ratios
:phel
[true 0/2
true 1/2
true -1/2]
:default
[false 0/2
false 1/2
Expand Down
1 change: 1 addition & 0 deletions test/clojure/core_test/inc.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#?(:clj (is (p/thrown? (inc Long/MAX_VALUE)))
:cljr (is (p/thrown? (inc Int64/MaxValue)))
:cljs (is (= (inc js/Number.MAX_SAFE_INTEGER) (+ 2 js/Number.MAX_SAFE_INTEGER)))
:phel (is (= (inc php/PHP_INT_MAX) (+ 2 php/PHP_INT_MAX)))
:lpy nil ; Python integers cannot overflow
:default (is false "overflow untested")))

Expand Down
9 changes: 6 additions & 3 deletions test/clojure/core_test/int_qmark.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,13 @@
false ##Inf
false ##-Inf
false ##NaN
#?@(:cljs [true] :lpy [true] :default [false]) 0N
#?@(:cljs [true] :lpy [true] :default [false]) 1N
#?@(:cljs [true] :lpy [true] :default [false]) -1N
#?@(:cljs [true] :lpy [true] :phel [true] :default [false]) 0N
#?@(:cljs [true] :lpy [true] :phel [true] :default [false]) 1N
#?@(:cljs [true] :lpy [true] :phel [true] :default [false]) -1N
#?@(:cljs []
:phel [false 0/2
false 1/2
false -1/2]
:default
[true 0/2
false 1/2
Expand Down
4 changes: 4 additions & 0 deletions test/clojure/core_test/integer_qmark.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
true 1N
true -1N
#?@(:cljs []
:phel
[false 0/2
false 1/2
false -1/2]
:default
[true 0/2
false 1/2
Expand Down
Loading