Compare commits
2 Commits
7c55b76dae
...
bd843d2219
| Author | SHA1 | Date | |
|---|---|---|---|
| bd843d2219 | |||
| d76aa17b38 |
31
CLAUDE.md
31
CLAUDE.md
@@ -42,15 +42,34 @@ When making changes:
|
|||||||
7. **Fix language limitations**: If you encounter parser/type system limitations, fix them (without regressions on guarantees or speed)
|
7. **Fix language limitations**: If you encounter parser/type system limitations, fix them (without regressions on guarantees or speed)
|
||||||
8. **Git commits**: Always use `--no-gpg-sign` flag
|
8. **Git commits**: Always use `--no-gpg-sign` flag
|
||||||
|
|
||||||
### Post-work checklist (run after each major piece of work)
|
### Post-work checklist (run after each committable change)
|
||||||
|
|
||||||
|
**MANDATORY: Run the full validation script after every committable change:**
|
||||||
|
```bash
|
||||||
|
./scripts/validate.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
This script runs ALL of the following checks and will fail if any regress:
|
||||||
|
1. `cargo check` — no Rust compilation errors
|
||||||
|
2. `cargo test` — all Rust tests pass (currently 387)
|
||||||
|
3. `cargo build --release` — release binary builds
|
||||||
|
4. `lux test` on every package (path, frontmatter, xml, rss, markdown) — all 286 package tests pass
|
||||||
|
5. `lux check` on every package — type checking + lint passes
|
||||||
|
|
||||||
|
If `validate.sh` is not available or you need to run manually:
|
||||||
```bash
|
```bash
|
||||||
nix develop --command cargo check # No Rust errors
|
nix develop --command cargo check # No Rust errors
|
||||||
nix develop --command cargo test # All tests pass (currently 381)
|
nix develop --command cargo test # All Rust tests pass
|
||||||
./target/release/lux check # Type check + lint all .lux files
|
nix develop --command cargo build --release # Build release binary
|
||||||
./target/release/lux fmt # Format all .lux files
|
cd ../packages/path && ../../lang/target/release/lux test # Package tests
|
||||||
./target/release/lux lint # Standalone lint pass
|
cd ../packages/frontmatter && ../../lang/target/release/lux test
|
||||||
|
cd ../packages/xml && ../../lang/target/release/lux test
|
||||||
|
cd ../packages/rss && ../../lang/target/release/lux test
|
||||||
|
cd ../packages/markdown && ../../lang/target/release/lux test
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Do NOT commit if any check fails.** Fix the issue first.
|
||||||
|
|
||||||
### Commit after every piece of work
|
### Commit after every piece of work
|
||||||
**After completing each logical unit of work, commit immediately.** Do not let changes accumulate uncommitted across multiple features. Each commit should be a single logical change (one feature, one bugfix, etc.). Use `--no-gpg-sign` flag for all commits.
|
**After completing each logical unit of work, commit immediately.** Do not let changes accumulate uncommitted across multiple features. Each commit should be a single logical change (one feature, one bugfix, etc.). Use `--no-gpg-sign` flag for all commits.
|
||||||
|
|
||||||
@@ -109,7 +128,7 @@ When working on any major task that involves writing Lux code, **document every
|
|||||||
## Code Quality
|
## Code Quality
|
||||||
|
|
||||||
- Fix all compiler warnings before committing
|
- Fix all compiler warnings before committing
|
||||||
- Ensure all tests pass (currently 381 tests)
|
- Ensure all tests pass (currently 387 tests)
|
||||||
- Add new tests when adding features
|
- Add new tests when adding features
|
||||||
- Keep examples and documentation in sync
|
- Keep examples and documentation in sync
|
||||||
|
|
||||||
|
|||||||
214
Cargo.lock
generated
214
Cargo.lock
generated
@@ -135,16 +135,6 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "core-foundation"
|
|
||||||
version = "0.10.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
|
|
||||||
dependencies = [
|
|
||||||
"core-foundation-sys",
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation-sys"
|
name = "core-foundation-sys"
|
||||||
version = "0.8.7"
|
version = "0.8.7"
|
||||||
@@ -235,7 +225,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -297,21 +287,6 @@ version = "0.1.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "foreign-types"
|
|
||||||
version = "0.3.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
|
||||||
dependencies = [
|
|
||||||
"foreign-types-shared",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "foreign-types-shared"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "form_urlencoded"
|
name = "form_urlencoded"
|
||||||
version = "1.2.2"
|
version = "1.2.2"
|
||||||
@@ -552,16 +527,17 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper-tls"
|
name = "hyper-rustls"
|
||||||
version = "0.5.0"
|
version = "0.24.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
|
checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"futures-util",
|
||||||
|
"http",
|
||||||
"hyper",
|
"hyper",
|
||||||
"native-tls",
|
"rustls",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-native-tls",
|
"tokio-rustls",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -843,23 +819,6 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "native-tls"
|
|
||||||
version = "0.2.16"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9d5d26952a508f321b4d3d2e80e78fc2603eaefcdf0c30783867f19586518bdc"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"log",
|
|
||||||
"openssl",
|
|
||||||
"openssl-probe",
|
|
||||||
"openssl-sys",
|
|
||||||
"schannel",
|
|
||||||
"security-framework",
|
|
||||||
"security-framework-sys",
|
|
||||||
"tempfile",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nibble_vec"
|
name = "nibble_vec"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -905,50 +864,6 @@ version = "1.21.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "openssl"
|
|
||||||
version = "0.10.75"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 2.10.0",
|
|
||||||
"cfg-if",
|
|
||||||
"foreign-types",
|
|
||||||
"libc",
|
|
||||||
"once_cell",
|
|
||||||
"openssl-macros",
|
|
||||||
"openssl-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "openssl-macros"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "openssl-probe"
|
|
||||||
version = "0.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "openssl-sys"
|
|
||||||
version = "0.9.111"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321"
|
|
||||||
dependencies = [
|
|
||||||
"cc",
|
|
||||||
"libc",
|
|
||||||
"pkg-config",
|
|
||||||
"vcpkg",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.12.5"
|
version = "0.12.5"
|
||||||
@@ -1203,15 +1118,15 @@ dependencies = [
|
|||||||
"http",
|
"http",
|
||||||
"http-body",
|
"http-body",
|
||||||
"hyper",
|
"hyper",
|
||||||
"hyper-tls",
|
"hyper-rustls",
|
||||||
"ipnet",
|
"ipnet",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
"mime",
|
"mime",
|
||||||
"native-tls",
|
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
|
"rustls",
|
||||||
"rustls-pemfile",
|
"rustls-pemfile",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -1219,15 +1134,30 @@ dependencies = [
|
|||||||
"sync_wrapper",
|
"sync_wrapper",
|
||||||
"system-configuration",
|
"system-configuration",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-native-tls",
|
"tokio-rustls",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
"url",
|
"url",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"wasm-bindgen-futures",
|
"wasm-bindgen-futures",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
|
"webpki-roots",
|
||||||
"winreg",
|
"winreg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ring"
|
||||||
|
version = "0.17.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"cfg-if",
|
||||||
|
"getrandom 0.2.17",
|
||||||
|
"libc",
|
||||||
|
"untrusted",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rusqlite"
|
name = "rusqlite"
|
||||||
version = "0.31.0"
|
version = "0.31.0"
|
||||||
@@ -1252,7 +1182,19 @@ dependencies = [
|
|||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls"
|
||||||
|
version = "0.21.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"ring",
|
||||||
|
"rustls-webpki",
|
||||||
|
"sct",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1264,6 +1206,16 @@ dependencies = [
|
|||||||
"base64 0.21.7",
|
"base64 0.21.7",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-webpki"
|
||||||
|
version = "0.101.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
|
||||||
|
dependencies = [
|
||||||
|
"ring",
|
||||||
|
"untrusted",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustversion"
|
name = "rustversion"
|
||||||
version = "1.0.22"
|
version = "1.0.22"
|
||||||
@@ -1298,15 +1250,6 @@ version = "1.0.23"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
|
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "schannel"
|
|
||||||
version = "0.1.28"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1"
|
|
||||||
dependencies = [
|
|
||||||
"windows-sys 0.61.2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scopeguard"
|
name = "scopeguard"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
@@ -1314,26 +1257,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "security-framework"
|
name = "sct"
|
||||||
version = "3.6.0"
|
version = "0.7.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d17b898a6d6948c3a8ee4372c17cb384f90d2e6e912ef00895b14fd7ab54ec38"
|
checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.10.0",
|
"ring",
|
||||||
"core-foundation 0.10.1",
|
"untrusted",
|
||||||
"core-foundation-sys",
|
|
||||||
"libc",
|
|
||||||
"security-framework-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "security-framework-sys"
|
|
||||||
version = "2.16.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "321c8673b092a9a42605034a9879d73cb79101ed5fd117bc9a597b89b4e9e61a"
|
|
||||||
dependencies = [
|
|
||||||
"core-foundation-sys",
|
|
||||||
"libc",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1521,7 +1451,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
|
checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
"core-foundation 0.9.4",
|
"core-foundation",
|
||||||
"system-configuration-sys",
|
"system-configuration-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1545,7 +1475,7 @@ dependencies = [
|
|||||||
"getrandom 0.4.1",
|
"getrandom 0.4.1",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix",
|
"rustix",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1619,16 +1549,6 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tokio-native-tls"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
|
|
||||||
dependencies = [
|
|
||||||
"native-tls",
|
|
||||||
"tokio",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-postgres"
|
name = "tokio-postgres"
|
||||||
version = "0.7.16"
|
version = "0.7.16"
|
||||||
@@ -1655,6 +1575,16 @@ dependencies = [
|
|||||||
"whoami",
|
"whoami",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-rustls"
|
||||||
|
version = "0.24.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
|
||||||
|
dependencies = [
|
||||||
|
"rustls",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-util"
|
name = "tokio-util"
|
||||||
version = "0.7.18"
|
version = "0.7.18"
|
||||||
@@ -1750,6 +1680,12 @@ version = "0.2.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "untrusted"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "2.5.8"
|
version = "2.5.8"
|
||||||
@@ -1941,6 +1877,12 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "webpki-roots"
|
||||||
|
version = "0.25.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "whoami"
|
name = "whoami"
|
||||||
version = "2.1.1"
|
version = "2.1.1"
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ lsp-types = "0.94"
|
|||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
reqwest = { version = "0.11", features = ["blocking", "json"] }
|
reqwest = { version = "0.11", default-features = false, features = ["blocking", "json", "rustls-tls"] }
|
||||||
tiny_http = "0.12"
|
tiny_http = "0.12"
|
||||||
rusqlite = { version = "0.31", features = ["bundled"] }
|
rusqlite = { version = "0.31", features = ["bundled"] }
|
||||||
postgres = "0.19"
|
postgres = "0.19"
|
||||||
|
|||||||
42
flake.nix
42
flake.nix
@@ -14,6 +14,7 @@
|
|||||||
pkgs = import nixpkgs { inherit system overlays; };
|
pkgs = import nixpkgs { inherit system overlays; };
|
||||||
rustToolchain = pkgs.rust-bin.stable.latest.default.override {
|
rustToolchain = pkgs.rust-bin.stable.latest.default.override {
|
||||||
extensions = [ "rust-src" "rust-analyzer" ];
|
extensions = [ "rust-src" "rust-analyzer" ];
|
||||||
|
targets = [ "x86_64-unknown-linux-musl" ];
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
@@ -22,8 +23,8 @@
|
|||||||
rustToolchain
|
rustToolchain
|
||||||
cargo-watch
|
cargo-watch
|
||||||
cargo-edit
|
cargo-edit
|
||||||
pkg-config
|
# Static builds
|
||||||
openssl
|
pkgsStatic.stdenv.cc
|
||||||
# Benchmark tools
|
# Benchmark tools
|
||||||
hyperfine
|
hyperfine
|
||||||
poop
|
poop
|
||||||
@@ -65,14 +66,43 @@
|
|||||||
src = ./.;
|
src = ./.;
|
||||||
cargoLock.lockFile = ./Cargo.lock;
|
cargoLock.lockFile = ./Cargo.lock;
|
||||||
|
|
||||||
nativeBuildInputs = [ pkgs.pkg-config ];
|
|
||||||
buildInputs = [ pkgs.openssl ];
|
|
||||||
|
|
||||||
doCheck = false;
|
doCheck = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
# Benchmark scripts
|
packages.static = let
|
||||||
|
muslPkgs = import nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
crossSystem = {
|
||||||
|
config = "x86_64-unknown-linux-musl";
|
||||||
|
isStatic = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in muslPkgs.rustPlatform.buildRustPackage {
|
||||||
|
pname = "lux";
|
||||||
|
version = "0.1.0";
|
||||||
|
src = ./.;
|
||||||
|
cargoLock.lockFile = ./Cargo.lock;
|
||||||
|
|
||||||
|
CARGO_BUILD_TARGET = "x86_64-unknown-linux-musl";
|
||||||
|
CARGO_BUILD_RUSTFLAGS = "-C target-feature=+crt-static";
|
||||||
|
|
||||||
|
doCheck = false;
|
||||||
|
|
||||||
|
postInstall = ''
|
||||||
|
$STRIP $out/bin/lux 2>/dev/null || true
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
apps = {
|
apps = {
|
||||||
|
# Release automation
|
||||||
|
release = {
|
||||||
|
type = "app";
|
||||||
|
program = toString (pkgs.writeShellScript "lux-release" ''
|
||||||
|
exec ${self}/scripts/release.sh "$@"
|
||||||
|
'');
|
||||||
|
};
|
||||||
|
|
||||||
|
# Benchmark scripts
|
||||||
# Run hyperfine benchmark comparison
|
# Run hyperfine benchmark comparison
|
||||||
bench = {
|
bench = {
|
||||||
type = "app";
|
type = "app";
|
||||||
|
|||||||
179
scripts/release.sh
Executable file
179
scripts/release.sh
Executable file
@@ -0,0 +1,179 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Lux Release Script
|
||||||
|
# Builds a static binary, generates changelog, and creates a Gitea release.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./scripts/release.sh [version]
|
||||||
|
#
|
||||||
|
# Environment:
|
||||||
|
# GITEA_TOKEN - API token for git.qrty.ink (prompted if not set)
|
||||||
|
# GITEA_URL - Gitea instance URL (default: https://git.qrty.ink)
|
||||||
|
|
||||||
|
# cd to repo root (directory containing this script's parent)
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
cd "$SCRIPT_DIR/.."
|
||||||
|
|
||||||
|
GITEA_URL="${GITEA_URL:-https://git.qrty.ink}"
|
||||||
|
REPO_OWNER="blu"
|
||||||
|
REPO_NAME="lux"
|
||||||
|
API_BASE="$GITEA_URL/api/v1"
|
||||||
|
|
||||||
|
# Colors
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
CYAN='\033[0;36m'
|
||||||
|
BOLD='\033[1m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
info() { printf "${CYAN}::${NC} %s\n" "$1"; }
|
||||||
|
ok() { printf "${GREEN}ok${NC} %s\n" "$1"; }
|
||||||
|
warn() { printf "${YELLOW}!!${NC} %s\n" "$1"; }
|
||||||
|
err() { printf "${RED}error:${NC} %s\n" "$1" >&2; exit 1; }
|
||||||
|
|
||||||
|
# --- Determine version ---
|
||||||
|
VERSION="${1:-}"
|
||||||
|
if [ -z "$VERSION" ]; then
|
||||||
|
VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)".*/\1/')
|
||||||
|
info "Version from Cargo.toml: v$VERSION"
|
||||||
|
fi
|
||||||
|
# Ensure v prefix
|
||||||
|
[[ "$VERSION" == v* ]] || VERSION="v$VERSION"
|
||||||
|
TAG="$VERSION"
|
||||||
|
|
||||||
|
# --- Check for clean working tree ---
|
||||||
|
if [ -n "$(git status --porcelain)" ]; then
|
||||||
|
warn "Working tree has uncommitted changes:"
|
||||||
|
git status --short
|
||||||
|
printf "\n"
|
||||||
|
read -rp "Continue anyway? [y/N] " confirm
|
||||||
|
[[ "$confirm" =~ ^[Yy]$ ]] || exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Check if tag already exists ---
|
||||||
|
if git rev-parse "$TAG" >/dev/null 2>&1; then
|
||||||
|
err "Tag $TAG already exists. Bump version in Cargo.toml or choose a different version."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Generate changelog ---
|
||||||
|
info "Generating changelog..."
|
||||||
|
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
|
||||||
|
if [ -n "$LAST_TAG" ]; then
|
||||||
|
RANGE="$LAST_TAG..HEAD"
|
||||||
|
info "Changes since $LAST_TAG:"
|
||||||
|
else
|
||||||
|
RANGE="HEAD"
|
||||||
|
info "First release — summarizing recent commits:"
|
||||||
|
fi
|
||||||
|
|
||||||
|
CHANGELOG=$(git log "$RANGE" --pretty=format:"- %s" --no-merges 2>/dev/null | head -50 || true)
|
||||||
|
if [ -z "$CHANGELOG" ]; then
|
||||||
|
CHANGELOG="- Initial release"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Build static binary ---
|
||||||
|
info "Building static binary (nix build .#static)..."
|
||||||
|
nix build .#static
|
||||||
|
BINARY="result/bin/lux"
|
||||||
|
if [ ! -f "$BINARY" ]; then
|
||||||
|
err "Static binary not found at $BINARY"
|
||||||
|
fi
|
||||||
|
|
||||||
|
BINARY_SIZE=$(ls -lh "$BINARY" | awk '{print $5}')
|
||||||
|
BINARY_TYPE=$(file "$BINARY" | sed 's/.*: //')
|
||||||
|
ok "Binary: $BINARY_SIZE, $BINARY_TYPE"
|
||||||
|
|
||||||
|
# --- Prepare release artifact ---
|
||||||
|
ARTIFACT="/tmp/lux-${TAG}-linux-x86_64"
|
||||||
|
cp "$BINARY" "$ARTIFACT"
|
||||||
|
chmod +x "$ARTIFACT"
|
||||||
|
|
||||||
|
# --- Show release summary ---
|
||||||
|
printf "\n"
|
||||||
|
printf "${BOLD}═══ Release Summary ═══${NC}\n"
|
||||||
|
printf "\n"
|
||||||
|
printf " ${BOLD}Tag:${NC} %s\n" "$TAG"
|
||||||
|
printf " ${BOLD}Binary:${NC} %s (%s)\n" "lux-${TAG}-linux-x86_64" "$BINARY_SIZE"
|
||||||
|
printf " ${BOLD}Commit:${NC} %s\n" "$(git rev-parse --short HEAD)"
|
||||||
|
printf "\n"
|
||||||
|
printf "${BOLD}Changelog:${NC}\n"
|
||||||
|
printf "%s\n" "$CHANGELOG"
|
||||||
|
printf "\n"
|
||||||
|
|
||||||
|
# --- Confirm ---
|
||||||
|
read -rp "Create release $TAG? [y/N] " confirm
|
||||||
|
[[ "$confirm" =~ ^[Yy]$ ]] || { info "Aborted."; exit 0; }
|
||||||
|
|
||||||
|
# --- Get Gitea token ---
|
||||||
|
if [ -z "${GITEA_TOKEN:-}" ]; then
|
||||||
|
printf "\n"
|
||||||
|
info "Gitea API token required (create at $GITEA_URL/user/settings/applications)"
|
||||||
|
read -rsp "Token: " GITEA_TOKEN
|
||||||
|
printf "\n"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$GITEA_TOKEN" ]; then
|
||||||
|
err "No token provided"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Create and push tag ---
|
||||||
|
info "Creating tag $TAG..."
|
||||||
|
git tag -a "$TAG" -m "Release $TAG" --no-sign
|
||||||
|
ok "Tag created"
|
||||||
|
|
||||||
|
info "Pushing tag to origin..."
|
||||||
|
git push origin "$TAG"
|
||||||
|
ok "Tag pushed"
|
||||||
|
|
||||||
|
# --- Create Gitea release ---
|
||||||
|
info "Creating release on Gitea..."
|
||||||
|
|
||||||
|
RELEASE_BODY=$(printf "## Lux %s\n\n### Changes\n\n%s\n\n### Installation\n\n\`\`\`bash\ncurl -Lo lux %s/%s/%s/releases/download/%s/lux-linux-x86_64\nchmod +x lux\n./lux --version\n\`\`\`" \
|
||||||
|
"$TAG" "$CHANGELOG" "$GITEA_URL" "$REPO_OWNER" "$REPO_NAME" "$TAG")
|
||||||
|
|
||||||
|
RELEASE_JSON=$(jq -n \
|
||||||
|
--arg tag "$TAG" \
|
||||||
|
--arg name "Lux $TAG" \
|
||||||
|
--arg body "$RELEASE_BODY" \
|
||||||
|
'{tag_name: $tag, name: $name, body: $body, draft: false, prerelease: false}')
|
||||||
|
|
||||||
|
RELEASE_RESPONSE=$(curl -s -X POST \
|
||||||
|
"$API_BASE/repos/$REPO_OWNER/$REPO_NAME/releases" \
|
||||||
|
-H "Authorization: token $GITEA_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "$RELEASE_JSON")
|
||||||
|
|
||||||
|
RELEASE_ID=$(echo "$RELEASE_RESPONSE" | jq -r '.id // empty')
|
||||||
|
if [ -z "$RELEASE_ID" ]; then
|
||||||
|
echo "$RELEASE_RESPONSE" | jq . 2>/dev/null || echo "$RELEASE_RESPONSE"
|
||||||
|
err "Failed to create release"
|
||||||
|
fi
|
||||||
|
ok "Release created (id: $RELEASE_ID)"
|
||||||
|
|
||||||
|
# --- Upload binary ---
|
||||||
|
info "Uploading binary..."
|
||||||
|
UPLOAD_RESPONSE=$(curl -s -X POST \
|
||||||
|
"$API_BASE/repos/$REPO_OWNER/$REPO_NAME/releases/$RELEASE_ID/assets?name=lux-linux-x86_64" \
|
||||||
|
-H "Authorization: token $GITEA_TOKEN" \
|
||||||
|
-H "Content-Type: application/octet-stream" \
|
||||||
|
--data-binary "@$ARTIFACT")
|
||||||
|
|
||||||
|
ASSET_NAME=$(echo "$UPLOAD_RESPONSE" | jq -r '.name // empty')
|
||||||
|
if [ -z "$ASSET_NAME" ]; then
|
||||||
|
echo "$UPLOAD_RESPONSE" | jq . 2>/dev/null || echo "$UPLOAD_RESPONSE"
|
||||||
|
err "Failed to upload binary"
|
||||||
|
fi
|
||||||
|
ok "Binary uploaded: $ASSET_NAME"
|
||||||
|
|
||||||
|
# --- Done ---
|
||||||
|
printf "\n"
|
||||||
|
printf "${GREEN}${BOLD}Release $TAG published!${NC}\n"
|
||||||
|
printf "\n"
|
||||||
|
printf " ${BOLD}URL:${NC} %s/%s/%s/releases/tag/%s\n" "$GITEA_URL" "$REPO_OWNER" "$REPO_NAME" "$TAG"
|
||||||
|
printf " ${BOLD}Download:${NC} %s/%s/%s/releases/download/%s/lux-linux-x86_64\n" "$GITEA_URL" "$REPO_OWNER" "$REPO_NAME" "$TAG"
|
||||||
|
printf "\n"
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
rm -f "$ARTIFACT"
|
||||||
73
scripts/validate.sh
Executable file
73
scripts/validate.sh
Executable file
@@ -0,0 +1,73 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Lux Full Validation Script
|
||||||
|
# Runs all checks: Rust tests, package tests, type checking, formatting, linting.
|
||||||
|
# Run after every committable change to ensure no regressions.
|
||||||
|
|
||||||
|
# cd to repo root (directory containing this script's parent)
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
cd "$SCRIPT_DIR/.."
|
||||||
|
|
||||||
|
LUX="$(pwd)/target/release/lux"
|
||||||
|
PACKAGES_DIR="$(pwd)/../packages"
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
CYAN='\033[0;36m'
|
||||||
|
BOLD='\033[1m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
FAILED=0
|
||||||
|
TOTAL=0
|
||||||
|
|
||||||
|
step() {
|
||||||
|
TOTAL=$((TOTAL + 1))
|
||||||
|
printf "${CYAN}[%d]${NC} %s... " "$TOTAL" "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
ok() { printf "${GREEN}ok${NC} %s\n" "${1:-}"; }
|
||||||
|
fail() { printf "${RED}FAIL${NC} %s\n" "${1:-}"; FAILED=$((FAILED + 1)); }
|
||||||
|
|
||||||
|
# --- Rust checks ---
|
||||||
|
step "cargo check"
|
||||||
|
if nix develop --command cargo check 2>&1 | grep -q "Finished"; then ok; else fail; fi
|
||||||
|
|
||||||
|
step "cargo test"
|
||||||
|
OUTPUT=$(nix develop --command cargo test 2>&1 || true)
|
||||||
|
RESULT=$(echo "$OUTPUT" | grep "test result:" || echo "no result")
|
||||||
|
if echo "$RESULT" | grep -q "0 failed"; then ok "$RESULT"; else fail "$RESULT"; fi
|
||||||
|
|
||||||
|
# --- Build release binary ---
|
||||||
|
step "cargo build --release"
|
||||||
|
if nix develop --command cargo build --release 2>&1 | grep -q "Finished"; then ok; else fail; fi
|
||||||
|
|
||||||
|
# --- Package tests ---
|
||||||
|
for pkg in path frontmatter xml rss markdown; do
|
||||||
|
PKG_DIR="$PACKAGES_DIR/$pkg"
|
||||||
|
if [ -d "$PKG_DIR" ]; then
|
||||||
|
step "lux test ($pkg)"
|
||||||
|
OUTPUT=$(cd "$PKG_DIR" && "$LUX" test 2>&1 || true)
|
||||||
|
RESULT=$(echo "$OUTPUT" | grep "passed" | tail -1 || echo "no result")
|
||||||
|
if echo "$RESULT" | grep -q "passed"; then ok "$RESULT"; else fail "$RESULT"; fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# --- Lux check on packages ---
|
||||||
|
for pkg in path frontmatter xml rss markdown; do
|
||||||
|
PKG_DIR="$PACKAGES_DIR/$pkg"
|
||||||
|
if [ -d "$PKG_DIR" ]; then
|
||||||
|
step "lux check ($pkg)"
|
||||||
|
OUTPUT=$(cd "$PKG_DIR" && "$LUX" check 2>&1 || true)
|
||||||
|
RESULT=$(echo "$OUTPUT" | grep "passed" | tail -1 || echo "no result")
|
||||||
|
if echo "$RESULT" | grep -q "passed"; then ok; else fail "$RESULT"; fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# --- Summary ---
|
||||||
|
printf "\n${BOLD}═══ Validation Summary ═══${NC}\n"
|
||||||
|
if [ $FAILED -eq 0 ]; then
|
||||||
|
printf "${GREEN}All %d checks passed.${NC}\n" "$TOTAL"
|
||||||
|
else
|
||||||
|
printf "${RED}%d/%d checks failed.${NC}\n" "$FAILED" "$TOTAL"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@@ -1536,7 +1536,7 @@ impl TypeChecker {
|
|||||||
// Use the declared type if present, otherwise use inferred
|
// Use the declared type if present, otherwise use inferred
|
||||||
let final_type = if let Some(ref type_expr) = let_decl.typ {
|
let final_type = if let Some(ref type_expr) = let_decl.typ {
|
||||||
let declared = self.resolve_type(type_expr);
|
let declared = self.resolve_type(type_expr);
|
||||||
if let Err(e) = unify(&inferred, &declared) {
|
if let Err(e) = unify_with_env(&inferred, &declared, &self.env) {
|
||||||
self.errors.push(TypeError {
|
self.errors.push(TypeError {
|
||||||
message: format!(
|
message: format!(
|
||||||
"Variable '{}' has type {}, but declared type is {}: {}",
|
"Variable '{}' has type {}, but declared type is {}: {}",
|
||||||
@@ -1783,7 +1783,7 @@ impl TypeChecker {
|
|||||||
match op {
|
match op {
|
||||||
BinaryOp::Add => {
|
BinaryOp::Add => {
|
||||||
// Add supports both numeric types and string concatenation
|
// Add supports both numeric types and string concatenation
|
||||||
if let Err(e) = unify(&left_type, &right_type) {
|
if let Err(e) = unify_with_env(&left_type, &right_type, &self.env) {
|
||||||
self.errors.push(TypeError {
|
self.errors.push(TypeError {
|
||||||
message: format!("Operands of '{}' must have same type: {}", op, e),
|
message: format!("Operands of '{}' must have same type: {}", op, e),
|
||||||
span,
|
span,
|
||||||
@@ -1806,7 +1806,7 @@ impl TypeChecker {
|
|||||||
|
|
||||||
BinaryOp::Sub | BinaryOp::Mul | BinaryOp::Div | BinaryOp::Mod => {
|
BinaryOp::Sub | BinaryOp::Mul | BinaryOp::Div | BinaryOp::Mod => {
|
||||||
// Arithmetic: both operands must be same numeric type
|
// Arithmetic: both operands must be same numeric type
|
||||||
if let Err(e) = unify(&left_type, &right_type) {
|
if let Err(e) = unify_with_env(&left_type, &right_type, &self.env) {
|
||||||
self.errors.push(TypeError {
|
self.errors.push(TypeError {
|
||||||
message: format!("Operands of '{}' must have same type: {}", op, e),
|
message: format!("Operands of '{}' must have same type: {}", op, e),
|
||||||
span,
|
span,
|
||||||
@@ -1830,7 +1830,7 @@ impl TypeChecker {
|
|||||||
|
|
||||||
BinaryOp::Eq | BinaryOp::Ne => {
|
BinaryOp::Eq | BinaryOp::Ne => {
|
||||||
// Equality: operands must have same type
|
// Equality: operands must have same type
|
||||||
if let Err(e) = unify(&left_type, &right_type) {
|
if let Err(e) = unify_with_env(&left_type, &right_type, &self.env) {
|
||||||
self.errors.push(TypeError {
|
self.errors.push(TypeError {
|
||||||
message: format!("Operands of '{}' must have same type: {}", op, e),
|
message: format!("Operands of '{}' must have same type: {}", op, e),
|
||||||
span,
|
span,
|
||||||
@@ -1841,7 +1841,7 @@ impl TypeChecker {
|
|||||||
|
|
||||||
BinaryOp::Lt | BinaryOp::Le | BinaryOp::Gt | BinaryOp::Ge => {
|
BinaryOp::Lt | BinaryOp::Le | BinaryOp::Gt | BinaryOp::Ge => {
|
||||||
// Comparison: operands must be same orderable type
|
// Comparison: operands must be same orderable type
|
||||||
if let Err(e) = unify(&left_type, &right_type) {
|
if let Err(e) = unify_with_env(&left_type, &right_type, &self.env) {
|
||||||
self.errors.push(TypeError {
|
self.errors.push(TypeError {
|
||||||
message: format!("Operands of '{}' must have same type: {}", op, e),
|
message: format!("Operands of '{}' must have same type: {}", op, e),
|
||||||
span,
|
span,
|
||||||
@@ -1852,13 +1852,13 @@ impl TypeChecker {
|
|||||||
|
|
||||||
BinaryOp::And | BinaryOp::Or => {
|
BinaryOp::And | BinaryOp::Or => {
|
||||||
// Logical: both must be Bool
|
// Logical: both must be Bool
|
||||||
if let Err(e) = unify(&left_type, &Type::Bool) {
|
if let Err(e) = unify_with_env(&left_type, &Type::Bool, &self.env) {
|
||||||
self.errors.push(TypeError {
|
self.errors.push(TypeError {
|
||||||
message: format!("Left operand of '{}' must be Bool: {}", op, e),
|
message: format!("Left operand of '{}' must be Bool: {}", op, e),
|
||||||
span: left.span(),
|
span: left.span(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if let Err(e) = unify(&right_type, &Type::Bool) {
|
if let Err(e) = unify_with_env(&right_type, &Type::Bool, &self.env) {
|
||||||
self.errors.push(TypeError {
|
self.errors.push(TypeError {
|
||||||
message: format!("Right operand of '{}' must be Bool: {}", op, e),
|
message: format!("Right operand of '{}' must be Bool: {}", op, e),
|
||||||
span: right.span(),
|
span: right.span(),
|
||||||
@@ -1872,7 +1872,7 @@ impl TypeChecker {
|
|||||||
// right must be a function that accepts left's type
|
// right must be a function that accepts left's type
|
||||||
let result_type = Type::var();
|
let result_type = Type::var();
|
||||||
let expected_fn = Type::function(vec![left_type.clone()], result_type.clone());
|
let expected_fn = Type::function(vec![left_type.clone()], result_type.clone());
|
||||||
if let Err(e) = unify(&right_type, &expected_fn) {
|
if let Err(e) = unify_with_env(&right_type, &expected_fn, &self.env) {
|
||||||
self.errors.push(TypeError {
|
self.errors.push(TypeError {
|
||||||
message: format!(
|
message: format!(
|
||||||
"Pipe target must be a function accepting {}: {}",
|
"Pipe target must be a function accepting {}: {}",
|
||||||
@@ -1904,7 +1904,7 @@ impl TypeChecker {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
UnaryOp::Not => {
|
UnaryOp::Not => {
|
||||||
if let Err(e) = unify(&operand_type, &Type::Bool) {
|
if let Err(e) = unify_with_env(&operand_type, &Type::Bool, &self.env) {
|
||||||
self.errors.push(TypeError {
|
self.errors.push(TypeError {
|
||||||
message: format!("Operator '!' requires Bool operand: {}", e),
|
message: format!("Operator '!' requires Bool operand: {}", e),
|
||||||
span,
|
span,
|
||||||
@@ -1955,7 +1955,7 @@ impl TypeChecker {
|
|||||||
self.current_effects.clone(),
|
self.current_effects.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
match unify(&func_type, &expected_fn) {
|
match unify_with_env(&func_type, &expected_fn, &self.env) {
|
||||||
Ok(subst) => result_type.apply(&subst),
|
Ok(subst) => result_type.apply(&subst),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// Provide more detailed error message based on the type of mismatch
|
// Provide more detailed error message based on the type of mismatch
|
||||||
@@ -2032,7 +2032,7 @@ impl TypeChecker {
|
|||||||
let result_type = Type::var();
|
let result_type = Type::var();
|
||||||
let expected_fn = Type::function(arg_types, result_type.clone());
|
let expected_fn = Type::function(arg_types, result_type.clone());
|
||||||
|
|
||||||
if let Err(e) = unify(field_type, &expected_fn) {
|
if let Err(e) = unify_with_env(field_type, &expected_fn, &self.env) {
|
||||||
self.errors.push(TypeError {
|
self.errors.push(TypeError {
|
||||||
message: format!(
|
message: format!(
|
||||||
"Type mismatch in {}.{} call: {}",
|
"Type mismatch in {}.{} call: {}",
|
||||||
@@ -2104,7 +2104,7 @@ impl TypeChecker {
|
|||||||
for (i, (arg_type, (_, param_type))) in
|
for (i, (arg_type, (_, param_type))) in
|
||||||
arg_types.iter().zip(op.params.iter()).enumerate()
|
arg_types.iter().zip(op.params.iter()).enumerate()
|
||||||
{
|
{
|
||||||
if let Err(e) = unify(arg_type, param_type) {
|
if let Err(e) = unify_with_env(arg_type, param_type, &self.env) {
|
||||||
self.errors.push(TypeError {
|
self.errors.push(TypeError {
|
||||||
message: format!(
|
message: format!(
|
||||||
"Argument {} of '{}.{}' has type {}, expected {}: {}",
|
"Argument {} of '{}.{}' has type {}, expected {}: {}",
|
||||||
@@ -2137,6 +2137,7 @@ impl TypeChecker {
|
|||||||
|
|
||||||
fn infer_field(&mut self, object: &Expr, field: &Ident, span: Span) -> Type {
|
fn infer_field(&mut self, object: &Expr, field: &Ident, span: Span) -> Type {
|
||||||
let object_type = self.infer_expr(object);
|
let object_type = self.infer_expr(object);
|
||||||
|
let object_type = self.env.expand_type_alias(&object_type);
|
||||||
|
|
||||||
match &object_type {
|
match &object_type {
|
||||||
Type::Record(fields) => match fields.iter().find(|(n, _)| n == &field.name) {
|
Type::Record(fields) => match fields.iter().find(|(n, _)| n == &field.name) {
|
||||||
@@ -2217,7 +2218,7 @@ impl TypeChecker {
|
|||||||
// Check return type if specified
|
// Check return type if specified
|
||||||
let ret_type = if let Some(rt) = return_type {
|
let ret_type = if let Some(rt) = return_type {
|
||||||
let declared = self.resolve_type(rt);
|
let declared = self.resolve_type(rt);
|
||||||
if let Err(e) = unify(&body_type, &declared) {
|
if let Err(e) = unify_with_env(&body_type, &declared, &self.env) {
|
||||||
self.errors.push(TypeError {
|
self.errors.push(TypeError {
|
||||||
message: format!(
|
message: format!(
|
||||||
"Lambda body type {} doesn't match declared {}: {}",
|
"Lambda body type {} doesn't match declared {}: {}",
|
||||||
@@ -2283,7 +2284,7 @@ impl TypeChecker {
|
|||||||
span: Span,
|
span: Span,
|
||||||
) -> Type {
|
) -> Type {
|
||||||
let cond_type = self.infer_expr(condition);
|
let cond_type = self.infer_expr(condition);
|
||||||
if let Err(e) = unify(&cond_type, &Type::Bool) {
|
if let Err(e) = unify_with_env(&cond_type, &Type::Bool, &self.env) {
|
||||||
self.errors.push(TypeError {
|
self.errors.push(TypeError {
|
||||||
message: format!("If condition must be Bool, got {}: {}", cond_type, e),
|
message: format!("If condition must be Bool, got {}: {}", cond_type, e),
|
||||||
span: condition.span(),
|
span: condition.span(),
|
||||||
@@ -2293,7 +2294,7 @@ impl TypeChecker {
|
|||||||
let then_type = self.infer_expr(then_branch);
|
let then_type = self.infer_expr(then_branch);
|
||||||
let else_type = self.infer_expr(else_branch);
|
let else_type = self.infer_expr(else_branch);
|
||||||
|
|
||||||
match unify(&then_type, &else_type) {
|
match unify_with_env(&then_type, &else_type, &self.env) {
|
||||||
Ok(subst) => then_type.apply(&subst),
|
Ok(subst) => then_type.apply(&subst),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
self.errors.push(TypeError {
|
self.errors.push(TypeError {
|
||||||
@@ -2334,7 +2335,7 @@ impl TypeChecker {
|
|||||||
// Check guard if present
|
// Check guard if present
|
||||||
if let Some(ref guard) = arm.guard {
|
if let Some(ref guard) = arm.guard {
|
||||||
let guard_type = self.infer_expr(guard);
|
let guard_type = self.infer_expr(guard);
|
||||||
if let Err(e) = unify(&guard_type, &Type::Bool) {
|
if let Err(e) = unify_with_env(&guard_type, &Type::Bool, &self.env) {
|
||||||
self.errors.push(TypeError {
|
self.errors.push(TypeError {
|
||||||
message: format!("Match guard must be Bool: {}", e),
|
message: format!("Match guard must be Bool: {}", e),
|
||||||
span: guard.span(),
|
span: guard.span(),
|
||||||
@@ -2350,7 +2351,7 @@ impl TypeChecker {
|
|||||||
match &result_type {
|
match &result_type {
|
||||||
None => result_type = Some(body_type),
|
None => result_type = Some(body_type),
|
||||||
Some(prev) => {
|
Some(prev) => {
|
||||||
if let Err(e) = unify(prev, &body_type) {
|
if let Err(e) = unify_with_env(prev, &body_type, &self.env) {
|
||||||
self.errors.push(TypeError {
|
self.errors.push(TypeError {
|
||||||
message: format!(
|
message: format!(
|
||||||
"Match arm has incompatible type: expected {}, got {}: {}",
|
"Match arm has incompatible type: expected {}, got {}: {}",
|
||||||
@@ -2400,7 +2401,7 @@ impl TypeChecker {
|
|||||||
|
|
||||||
Pattern::Literal(lit) => {
|
Pattern::Literal(lit) => {
|
||||||
let lit_type = self.infer_literal(lit);
|
let lit_type = self.infer_literal(lit);
|
||||||
if let Err(e) = unify(&lit_type, expected) {
|
if let Err(e) = unify_with_env(&lit_type, expected, &self.env) {
|
||||||
self.errors.push(TypeError {
|
self.errors.push(TypeError {
|
||||||
message: format!("Pattern literal type mismatch: {}", e),
|
message: format!("Pattern literal type mismatch: {}", e),
|
||||||
span: lit.span,
|
span: lit.span,
|
||||||
@@ -2414,7 +2415,7 @@ impl TypeChecker {
|
|||||||
// For now, handle Option specially
|
// For now, handle Option specially
|
||||||
match name.name.as_str() {
|
match name.name.as_str() {
|
||||||
"None" => {
|
"None" => {
|
||||||
if let Err(e) = unify(expected, &Type::Option(Box::new(Type::var()))) {
|
if let Err(e) = unify_with_env(expected, &Type::Option(Box::new(Type::var())), &self.env) {
|
||||||
self.errors.push(TypeError {
|
self.errors.push(TypeError {
|
||||||
message: format!(
|
message: format!(
|
||||||
"None pattern doesn't match type {}: {}",
|
"None pattern doesn't match type {}: {}",
|
||||||
@@ -2427,7 +2428,7 @@ impl TypeChecker {
|
|||||||
}
|
}
|
||||||
"Some" => {
|
"Some" => {
|
||||||
let inner_type = Type::var();
|
let inner_type = Type::var();
|
||||||
if let Err(e) = unify(expected, &Type::Option(Box::new(inner_type.clone())))
|
if let Err(e) = unify_with_env(expected, &Type::Option(Box::new(inner_type.clone())), &self.env)
|
||||||
{
|
{
|
||||||
self.errors.push(TypeError {
|
self.errors.push(TypeError {
|
||||||
message: format!(
|
message: format!(
|
||||||
@@ -2456,7 +2457,7 @@ impl TypeChecker {
|
|||||||
|
|
||||||
Pattern::Tuple { elements, span } => {
|
Pattern::Tuple { elements, span } => {
|
||||||
let element_types: Vec<Type> = elements.iter().map(|_| Type::var()).collect();
|
let element_types: Vec<Type> = elements.iter().map(|_| Type::var()).collect();
|
||||||
if let Err(e) = unify(expected, &Type::Tuple(element_types.clone())) {
|
if let Err(e) = unify_with_env(expected, &Type::Tuple(element_types.clone()), &self.env) {
|
||||||
self.errors.push(TypeError {
|
self.errors.push(TypeError {
|
||||||
message: format!("Tuple pattern doesn't match type {}: {}", expected, e),
|
message: format!("Tuple pattern doesn't match type {}: {}", expected, e),
|
||||||
span: *span,
|
span: *span,
|
||||||
@@ -2506,7 +2507,7 @@ impl TypeChecker {
|
|||||||
|
|
||||||
if let Some(type_expr) = typ {
|
if let Some(type_expr) = typ {
|
||||||
let declared = self.resolve_type(type_expr);
|
let declared = self.resolve_type(type_expr);
|
||||||
if let Err(e) = unify(&value_type, &declared) {
|
if let Err(e) = unify_with_env(&value_type, &declared, &self.env) {
|
||||||
self.errors.push(TypeError {
|
self.errors.push(TypeError {
|
||||||
message: format!(
|
message: format!(
|
||||||
"Variable '{}' has type {}, but declared type is {}: {}",
|
"Variable '{}' has type {}, but declared type is {}: {}",
|
||||||
@@ -2549,7 +2550,7 @@ impl TypeChecker {
|
|||||||
let first_type = self.infer_expr(&elements[0]);
|
let first_type = self.infer_expr(&elements[0]);
|
||||||
for elem in &elements[1..] {
|
for elem in &elements[1..] {
|
||||||
let elem_type = self.infer_expr(elem);
|
let elem_type = self.infer_expr(elem);
|
||||||
if let Err(e) = unify(&first_type, &elem_type) {
|
if let Err(e) = unify_with_env(&first_type, &elem_type, &self.env) {
|
||||||
self.errors.push(TypeError {
|
self.errors.push(TypeError {
|
||||||
message: format!("List elements must have same type: {}", e),
|
message: format!("List elements must have same type: {}", e),
|
||||||
span,
|
span,
|
||||||
@@ -2855,7 +2856,7 @@ impl TypeChecker {
|
|||||||
// Check return type matches if specified
|
// Check return type matches if specified
|
||||||
if let Some(ref return_type_expr) = impl_method.return_type {
|
if let Some(ref return_type_expr) = impl_method.return_type {
|
||||||
let return_type = self.resolve_type(return_type_expr);
|
let return_type = self.resolve_type(return_type_expr);
|
||||||
if let Err(e) = unify(&body_type, &return_type) {
|
if let Err(e) = unify_with_env(&body_type, &return_type, &self.env) {
|
||||||
self.errors.push(TypeError {
|
self.errors.push(TypeError {
|
||||||
message: format!(
|
message: format!(
|
||||||
"Method '{}' body has type {}, but declared return type is {}: {}",
|
"Method '{}' body has type {}, but declared return type is {}: {}",
|
||||||
|
|||||||
Reference in New Issue
Block a user