Version: 0.1.0
Last Updated: 2025-11-19
Cure's Language Server Protocol (LSP) integration provides real-time SMT-based verification for:
x > 0 at compile time✅ Real-time feedback - See errors as you type, no compilation needed
✅ Concrete counterexamples - "x = -5 violates x > 0" instead of generic errors
✅ Automated fixes - One-click quick fixes for common issues
✅ Performance optimized - <100ms for cached checks, <500ms for new code
# macOS
brew install z3
# Or download from: https://github.com/Z3Prover/z3/releases
```
bash
z3 --version
# Should output: Z3 version 4.x.x.cure file-- Add to your LSP config
require('lspconfig').cure_ls.setup{
cmd = {'cure-lsp-server'},
filetypes = {'cure'},
settings = {
cure = {
smt = {
enabled = true,
solver = 'z3',
timeout = 500 -- ms
}
}
}
}
(use-package lsp-mode
:hook (cure-mode . lsp)
:custom
(lsp-cure-smt-enabled t)
(lsp-cure-smt-timeout 500))
Define constrained types:
type Positive = Int when x > 0
type Percentage = Int when x >= 0 and x <= 100
type NonEmptyList(T) = List(T) when length(xs) > 0
Get instant feedback:
def divide(a: Int, b: Positive): Int = a / b ✅
def bad_divide(a: Int, b: Int): Int =
divide(a, b) // ❌ Error: b may not be positive
// Counterexample: b = -5 violates b > 0
Hover over any variable to see its refinement type:
def process(n: Positive) =
let x = n + 1 // Hover shows: x: Int when x > 1
Hover display:
```cure
x: Int when x > 1
Refinement Type
This variable has a refined type with the constraint:
- x > 1
The SMT solver will verify this constraint is satisfied.
### 3. Pattern Exhaustiveness
**Automatic checking:**
```cure
match n with
| 0 -> "zero"
| n when n > 0 -> "positive"
end
// ❌ Error: Pattern match is not exhaustive
// Missing case: n when n < 0
With quick fix:
match n with
| 0 -> "zero"
| n when n > 0 -> "positive"
| n when n < 0 -> "negative" // Quick fix adds this
end
When SMT finds an issue, you get actionable fixes:
// Before
def divide(a: Int, b: Int): Int = a / b
// Quick fix: Add guard clause
def divide(a: Int, b: Int) when b /= 0: Int = a / b
def divide(a: Int, b: Int): Int =
assert b /= 0 // Quick fix adds this
a / b
// Error: Cannot prove n > 10 implies n > 5
// Quick fix: Relax n > 10 to n >= 10
// Error: Expected Int, got Float
// Quick fix: Add to_int(value)
Configure timeouts based on your workflow:
% In your LSP config
#{
timeouts => #{
lsp => 500, % Interactive editing (default)
compile => 5000, % Full compilation
test => 10000 % Running tests
}
}
#{
cache => #{
enabled => true,
max_size => 1000, % Max cached queries
ttl_seconds => 3600 % Cache lifetime
}
}
#{
solver => z3, % Currently only Z3 supported
solver_path => "/usr/bin/z3" % Custom path if needed
}
✅ Good: Simple constraints
type Positive = Int when x > 0
type Even = Int when x mod 2 == 0
✅ Good: Composite constraints
type PositiveEven = Int when x > 0 and x mod 2 == 0
⚠️ Careful: Complex constraints
// May be slow
type Prime = Int when is_prime(x) // Non-linear
❌ Avoid: Undecidable constraints
// SMT may timeout
type Halting = Int when terminates(f, x)
| Complexity | SMT Time | Example |
|---|---|---|
| Simple comparison | <10ms | x > 0, x <= 100 |
| Linear arithmetic | <50ms | x + y > z, 2*x < y |
| Quantifiers (bounded) | <200ms | forall i. 0 <= i < n => arr[i] > 0 |
| Non-linear | <500ms | x * y > z (may timeout) |
Problem: Constraint not satisfied
Available fixes:
- Add guard clause: when x > 0
- Add assertion: assert x > 0
- Relax constraint: x > 0 → x >= 0
Problem: Missing pattern cases
Available fixes:
- Add missing case (SMT provides counterexample)
- Add wildcard: _ -> error("unhandled")
Problem: Type doesn't match
Available fixes:
- Add type annotation: : Positive
- Add type conversion: to_int(), to_float()
Problem: Variable not in scope
Available fixes:
- Add as parameter
- Define with let
Cache hit (instant): <10ms
Small edit (incremental): <100ms
New constraint (full solve): <500ms
Complex constraint: May timeout at 500ms
// Good: Stable constraints
type UserId = Int when x > 0
def process_user(id: UserId) = ... // Cached after first check
// Bad: Dynamic constraints
type DynamicRange(n) = Int when x >= 0 and x < n // Re-verified each time
// SMT batches constraints from same context
def process_many(xs: List(Positive)) = // All checked together
xs.map(fn x -> x + 1)
// Faster
type Positive = Int when x > 0
// Slower (quantifier)
type AllPositive = List(Int) when
forall i. 0 <= i < length(xs) => xs[i] > 0
Check LSP statistics:
cure_lsp_performance:get_stats().
% => #{
% queued => 45,
% processed => 120,
% avg_verify_time_ms => 85,
% cache_hit_rate => 0.82
% }
Error: Z3 solver not found
Solution:
1. Install Z3: sudo apt install z3 or brew install z3
2. Verify: z3 --version
3. Set path in config if in non-standard location
Error: SMT verification timed out
Solutions:
1. Simplify the constraint
2. Increase timeout in settings
3. Split complex constraints into simpler ones
4. Use runtime checks instead: assert x > 0
Symptom: LSP using >1GB RAM
Solutions:
1. Restart LSP server
2. Reduce cache size in config
3. Disable SMT for large files: // @smt-disable
Symptom: Lag when typing
Solutions:
1. Check cache hit rate (should be >80%)
2. Increase batch timeout
3. Disable SMT temporarily: Toggle in IDE
Enable verbose logging:
CURE_LSP_LOG_LEVEL=debug cure-lsp-server
Check logs:
tail -f ~/.cure/lsp.log
// @smt-disable
def unsafe_operation(x: Int) =
// SMT verification skipped here
x / 0 // Would normally error
#{
solver_args => [
"timeout=1000",
"smt.mbqi=false"
]
}
LSP automatically uses incremental solving:
- Persistent Z3 process (avoids 50ms startup)
- Push/pop scopes for edits
- Hash-based cache invalidation
Large files verified in background:
cure_lsp_performance:verify_async(Uri, AST, lsp).
Results arrive asynchronously without blocking editor.
Q: Does SMT verification guarantee no runtime errors?
A: SMT proves properties about types, but runtime checks are still needed for:
- External input validation
- Resource limits (memory, disk)
- Concurrent behavior
Q: Can I use CVC5 instead of Z3?
A: Not yet. Z3 is currently the only supported solver.
Q: Does SMT work with separate compilation?
A: Yes! Constraint verification is modular and works across module boundaries.
Q: What happens if SMT times out?
A: The constraint is treated as "unknown" - no error, but no proof either. Use runtime checks as fallback.
Q: Can SMT verify recursive functions?
A: Limited support. Simple recursion works, but complex recursion may timeout. Use termination measures explicitly.
Happy verifying! 🎉
For issues or questions: https://github.com/cure-lang/cure/issues