Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
100 commits
Select commit Hold shift + click to select a range
5b51055
adds main with log to all passing tests
roryc89 Mar 11, 2026
0432e1b
up timeout
roryc89 Mar 11, 2026
0766153
adds codegen test
roryc89 Mar 11, 2026
f3c40bc
adds support debug
roryc89 Mar 11, 2026
6bcdee3
more codegen fixtures/passing tests passing
roryc89 Mar 11, 2026
5df9b1f
move to typescript
roryc89 Mar 12, 2026
87300fd
move to typescript
roryc89 Mar 12, 2026
88d9368
test do notiation code gen
roryc89 Mar 12, 2026
002450a
prefix dicts
roryc89 Mar 12, 2026
1df7a70
all ts output from passing fixture tests passing
roryc89 Mar 12, 2026
7ba6a24
adds derive snapshot tests
roryc89 Mar 12, 2026
26ad2e4
update snapshots
roryc89 Mar 12, 2026
a500660
adds original compiler output
roryc89 Mar 12, 2026
1c1ae94
adds original compiler output
roryc89 Mar 12, 2026
85116c7
adds original compiler output
roryc89 Mar 12, 2026
a9daf21
adds original compiler output
roryc89 Mar 12, 2026
b086980
readds typechecking
roryc89 Mar 12, 2026
b84f40e
removes typescript references
roryc89 Mar 12, 2026
b51a82f
remove types from snaps
roryc89 Mar 12, 2026
c59bf4b
js. not ts
roryc89 Mar 12, 2026
7884f2c
add comparison check
roryc89 Mar 12, 2026
1c687a4
no more typechecking errors in compiler
roryc89 Mar 12, 2026
b35dc57
add original compiler output for codegen
roryc89 Mar 13, 2026
e24e7e7
update snaps
roryc89 Mar 13, 2026
12a8fbe
make snaps match original compiler
roryc89 Mar 13, 2026
bc34c68
more similar codegen to original compiler
roryc89 Mar 13, 2026
6efd995
add script to build original output
roryc89 Mar 13, 2026
b9fd0a7
adds new original compiler output
roryc89 Mar 13, 2026
8c49004
all codegen tests in fixtures
roryc89 Mar 13, 2026
665afac
replace codegen snapshots for normalised test
roryc89 Mar 13, 2026
a232c81
codegen mostly matching original compiler
roryc89 Mar 13, 2026
1ecf447
adds codegen snapshots
roryc89 Mar 13, 2026
ee7f8ec
adds codegen snapshots
roryc89 Mar 13, 2026
ec62b13
adds prelude codegen test
roryc89 Mar 13, 2026
e6c9658
better prelude codgen test
roryc89 Mar 13, 2026
384333d
prelude almost building
roryc89 Mar 14, 2026
3ad1470
all prelude output matching
roryc89 Mar 14, 2026
bc050e2
adds prelude runtime test
roryc89 Mar 14, 2026
39a89e1
Fix declaration ordering and do-notation discard for runtime correctness
roryc89 Mar 14, 2026
a726bde
update snapshots
roryc89 Mar 14, 2026
6601467
Fix dict resolution for overlapping instances, Row.Cons bindings, and…
roryc89 Mar 14, 2026
a975771
Fix Reflectable/Reifiable dict resolution, generic derive codegen, an…
roryc89 Mar 14, 2026
3fe007f
Fix do-notation Bind constraint for rebindable do (plain bind function)
roryc89 Mar 14, 2026
9dc20e4
Revert "Fix do-notation Bind constraint for rebindable do (plain bind…
roryc89 Mar 14, 2026
83ae90d
import Bind class in tests
roryc89 Mar 14, 2026
78b75e4
dont compare on build_fixture_original_compiler_passing
roryc89 Mar 14, 2026
041db81
Partial constraint codegen, TCO dict-wrapper skip, and codegen improv…
roryc89 Mar 15, 2026
6f891b0
Fix type alias expansion in pre-insertion, stale TyVarId panics, and …
roryc89 Mar 16, 2026
e521211
Skip dict application for local bindings and shadowed module-level names
roryc89 Mar 16, 2026
91e5a36
Fix operator alias resolution, constructor pattern matching, and zero…
roryc89 Mar 16, 2026
08b108b
Fix operator constraint shadowing, record update wildcards, and hybri…
roryc89 Mar 16, 2026
2fdf3c7
Fix unsafePartial $ detection, backtick operator local shadowing, and…
roryc89 Mar 16, 2026
360cb82
Fix module re-export codegen for import-all and explicit import chains
roryc89 Mar 16, 2026
821a29a
better error messages
roryc89 Mar 16, 2026
0d7a9ee
removes TypeAliasShadowsImport lib as not valid
roryc89 Mar 16, 2026
0da1091
Add Functor/Traversable derive codegen, TypeEquals dict resolution, r…
roryc89 Mar 17, 2026
63d2cf6
label expected and found
roryc89 Mar 17, 2026
96dd9e8
adds more completion tests
roryc89 Mar 17, 2026
48e4be5
more completion
roryc89 Mar 17, 2026
a79ba0b
RecordLabelMismatch
roryc89 Mar 17, 2026
0edb72c
fix some failing tests
roryc89 Mar 17, 2026
708a9e8
better unification errors for records
roryc89 Mar 17, 2026
aa94ff8
faster cached builds
roryc89 Mar 17, 2026
9c72b00
allow node failures
roryc89 Mar 17, 2026
6bd8b4f
fix lsp ctr imports
roryc89 Mar 17, 2026
e796516
more nested record checks
roryc89 Mar 17, 2026
189c9d5
newline record mismatches
roryc89 Mar 17, 2026
09bcacd
more coerible work
roryc89 Mar 17, 2026
a0cf648
adds typed holes
roryc89 Mar 17, 2026
ab17b9a
oa building again
roryc89 Mar 17, 2026
e9c275d
adds js mismatch test
roryc89 Mar 17, 2026
35b13e5
all tests passing
roryc89 Mar 17, 2026
0a38068
faster codegen
roryc89 Mar 17, 2026
081cc0d
no sequential mode
roryc89 Mar 17, 2026
96cc0eb
adds output dir to lsp
roryc89 Mar 18, 2026
b5e5a88
parallelize codegen with pre-computed global tables
roryc89 Mar 18, 2026
d1c1e09
adds output dir to diagnostics
roryc89 Mar 18, 2026
a372dc8
output dir for lsp
roryc89 Mar 18, 2026
a4ca4d5
fix ctr sibling imports
roryc89 Mar 18, 2026
a96864e
passing and failing tests OK
roryc89 Mar 18, 2026
0b44b28
fixture tests passing
roryc89 Mar 18, 2026
68312a3
share normalize_js
roryc89 Mar 18, 2026
471e2e8
fail on js mismatch
roryc89 Mar 18, 2026
285206b
adds fail fast
roryc89 Mar 18, 2026
65e7efa
js mismatch before node
roryc89 Mar 18, 2026
8fbac7f
remove js mismatch check
roryc89 Mar 18, 2026
16a6b59
fixes partial constraints across modules
roryc89 Mar 18, 2026
33cf673
inline codgen and parallelise clean module export loading
roryc89 Mar 18, 2026
5c78af2
fuse codegen with typechecking
roryc89 Mar 18, 2026
33f9a14
adds outputDirCommand
roryc89 Mar 18, 2026
a781278
more codegen
roryc89 Mar 18, 2026
34148de
more codegen
roryc89 Mar 18, 2026
e700096
add client readme
roryc89 Mar 18, 2026
801f4b9
ignore packaged client
roryc89 Mar 18, 2026
594c4b6
allow some node fails
roryc89 Mar 18, 2026
0aec302
update comment
roryc89 Mar 18, 2026
6f588b5
ignore passing output
roryc89 Mar 18, 2026
7ce25d4
track errors
roryc89 Mar 18, 2026
60c28e0
oa building
roryc89 Mar 18, 2026
fc7bd01
remove debug logs
roryc89 Mar 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,13 @@ Thumbs.db
/editors/code/out
/editors/code/.vscode-test
/editors/code/package-lock.json
/editors/code/pfc-lsp-*

# purs
/output

# test artifacts
tests/fixtures/original-compiler/passing/*.output.js
tests/fixtures/original-compiler/passing/*/*.output.js
tests/fixtures/original-compiler/passing/*.error.txt
tests/fixtures/original-compiler/passing/*/*.error.txt
10 changes: 7 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@ miette = { version = "7.6", features = ["fancy"] }
thiserror = "2.0"
lalrpop-util = "0.22"
glob = "0.3"
swc_ecma_parser = "34.0.0"
swc_ecma_ast = "20.0.1"
swc_common = "18.0.1"
swc_ecma_parser = "35.0.0"
swc_ecma_ast = "21.0.0"
swc_common = "19.0.0"
swc_ecma_codegen = "24.0.0"
ntest_timeout = "0.9.5"
rayon = "1.10"
stacker = "0.1"
mimalloc = { version = "0.1", default-features = false }
tower-lsp = "0.20"
tokio = { version = "1", features = ["full"] }
Expand All @@ -43,3 +45,5 @@ insta = "1.34"
criterion = "0.5"
proptest = "1.4"
regex = "1"
tempfile = "3.27.0"
wait-timeout = "0.2.1"
16 changes: 14 additions & 2 deletions editors/code/package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
{
"name": "pfc-lsp",
"displayName": "PureScript Fast Compiler",
"displayName": "PureScript Fast Compiler LSP Client",
"description": "VS Code client for the pfc language server",
"version": "0.0.1",
"publisher": "pfc",
"publisher": "OxfordAbstracts",
"repository": "https://github.com/OxfordAbstracts/purescript-fast-compiler",
"engines": {
"vscode": "^1.75.0"
},
Expand Down Expand Up @@ -49,6 +50,16 @@
"type": "string",
"default": "ragu sources",
"description": "Shell command that outputs PureScript source file paths (one per line). Example: find src .spago/p -name '*.purs'"
},
"pfc.outputDir": {
"type": "string",
"default": "",
"description": "Output directory for generated JavaScript. When set, the LSP will generate JS code on save. Example: output"
},
"pfc.outputDirCommand": {
"type": "string",
"default": "ragu output-dir",
"description": "Shell command that outputs the JS output directory path. When set, overrides pfc.outputDir. Example: spago path output"
}
}
}
Expand All @@ -61,6 +72,7 @@
"vscode-languageclient": "^9.0.1"
},
"devDependencies": {
"@types/node": "^20.0.0",
"@types/vscode": "^1.75.0",
"typescript": "^5.0.0"
}
Expand Down
4 changes: 4 additions & 0 deletions editors/code/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,15 @@ export function activate(context: vscode.ExtensionContext) {
const config = vscode.workspace.getConfiguration("pfc");
const serverPath = config.get<string>("serverPath", "pfc");
const sourcesCommand = config.get<string>("sourcesCommand", "");
const outputDir = config.get<string>("outputDir", "");

const args = ["lsp"];
if (sourcesCommand) {
args.push("--sources-cmd", sourcesCommand);
}
if (outputDir) {
args.push("--output-dir", outputDir);
}

const serverOptions: ServerOptions = {
command: serverPath,
Expand Down
118 changes: 102 additions & 16 deletions src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,9 @@ pub enum Expr {
/// Typed hole: ?hole
Hole { span: Span, name: Ident },

/// Wildcard: _ (anonymous argument, NOT a typed hole)
Wildcard { span: Span },

/// Array literal: [1, 2, 3]
Array { span: Span, elements: Vec<Expr> },

Expand Down Expand Up @@ -580,6 +583,7 @@ impl Expr {
| Expr::RecordUpdate { span, .. }
| Expr::TypeAnnotation { span, .. }
| Expr::Hole { span, .. }
| Expr::Wildcard { span, .. }
| Expr::Array { span, .. }
| Expr::Negate { span, .. }
| Expr::AsPattern { span, .. }
Expand Down Expand Up @@ -1697,6 +1701,10 @@ impl Converter {
// --- Expression conversion ---

fn convert_expr(&mut self, expr: &cst::Expr) -> Expr {
stacker::maybe_grow(32 * 1024, 2 * 1024 * 1024, || self.convert_expr_impl(expr))
}

fn convert_expr_impl(&mut self, expr: &cst::Expr) -> Expr {
match expr {
cst::Expr::Var { span, name } => Expr::Var {
span: *span,
Expand Down Expand Up @@ -1931,10 +1939,87 @@ impl Converter {
.map(|f| self.convert_record_field(f))
.collect(),
},
cst::Expr::RecordAccess { span, expr, field } => Expr::RecordAccess {
span: *span,
expr: Box::new(self.convert_expr(expr)),
field: field.clone(),
cst::Expr::RecordAccess { span, expr, field } => {
// _.x (record accessor section) → \$_arg -> $_arg.x
if Self::is_wildcard(expr) {
let param_name = intern("$_arg");
let mut scope = HashMap::new();
scope.insert(param_name, *span);
self.local_scopes.push(scope);
let param_expr = Expr::Var {
span: expr.span(),
name: QualifiedIdent {
module: None,
name: param_name,
},
definition_site: DefinitionSite::Local(*span),
};
let body = Expr::RecordAccess {
span: *span,
expr: Box::new(param_expr),
field: field.clone(),
};
self.local_scopes.pop();
Expr::Lambda {
span: *span,
binders: vec![Binder::Var {
span: *span,
name: cst::Spanned {
span: *span,
value: param_name,
},
}],
body: Box::new(body),
}
} else {
// Handle chained accessor on wildcard: _.x.y → \$_arg -> $_arg.x.y
let mut chain = vec![field.clone()];
let mut inner = expr.as_ref();
while let cst::Expr::RecordAccess { expr: e, field: f, .. } = inner {
chain.push(f.clone());
inner = e.as_ref();
}
if Self::is_wildcard(inner) {
let param_name = intern("$_arg");
let mut scope = HashMap::new();
scope.insert(param_name, *span);
self.local_scopes.push(scope);
let mut body = Expr::Var {
span: inner.span(),
name: QualifiedIdent {
module: None,
name: param_name,
},
definition_site: DefinitionSite::Local(*span),
};
// Apply fields in reverse (innermost first): _.x.y → ($__arg).x.y
for f in chain.iter().rev() {
body = Expr::RecordAccess {
span: *span,
expr: Box::new(body),
field: f.clone(),
};
}
self.local_scopes.pop();
Expr::Lambda {
span: *span,
binders: vec![Binder::Var {
span: *span,
name: cst::Spanned {
span: *span,
value: param_name,
},
}],
body: Box::new(body),
}
} else {
Expr::RecordAccess {
span: *span,
expr: Box::new(self.convert_expr(expr)),
field: field.clone(),
}
}
}
},
cst::Expr::RecordUpdate {
span,
Expand Down Expand Up @@ -1975,9 +2060,8 @@ impl Converter {
span: *span,
name: *name,
},
cst::Expr::Wildcard { span } => Expr::Hole {
cst::Expr::Wildcard { span } => Expr::Wildcard {
span: *span,
name: intern("_"),
},
cst::Expr::Array { span, elements } => Expr::Array {
span: *span,
Expand Down Expand Up @@ -2204,8 +2288,7 @@ impl Converter {
return result;
}

let wildcard_sym = interner::intern("_");
// Destructure App(App(op, left), right) to check for holes
// Destructure App(App(op, left), right) to check for wildcard sections
if let Expr::App {
span: outer_span,
func: outer_func,
Expand All @@ -2218,10 +2301,8 @@ impl Converter {
arg: left_arg,
} = *outer_func
{
let left_is_hole =
matches!(&*left_arg, Expr::Hole { name, .. } if *name == wildcard_sym);
let right_is_hole =
matches!(&*right_arg, Expr::Hole { name, .. } if *name == wildcard_sym);
let left_is_hole = matches!(&*left_arg, Expr::Wildcard { .. });
let right_is_hole = matches!(&*right_arg, Expr::Wildcard { .. });
if left_is_hole || right_is_hole {
// Valid section after rebalancing — desugar to lambda
let param_name = interner::intern("$_arg");
Expand Down Expand Up @@ -2278,10 +2359,7 @@ impl Converter {
// but emit error for safety
self.errors
.push(TypeError::IncorrectAnonymousArgument { span });
output.pop().unwrap_or(Expr::Hole {
span,
name: wildcard_sym,
})
output.pop().unwrap_or(Expr::Wildcard { span })
}

fn build_op_app(
Expand Down Expand Up @@ -2363,6 +2441,10 @@ impl Converter {
// --- Type expression conversion ---

fn convert_type_expr(&mut self, ty: &cst::TypeExpr) -> TypeExpr {
stacker::maybe_grow(32 * 1024, 2 * 1024 * 1024, || self.convert_type_expr_impl(ty))
}

fn convert_type_expr_impl(&mut self, ty: &cst::TypeExpr) -> TypeExpr {
match ty {
cst::TypeExpr::Var { span, name } => TypeExpr::Var {
span: *span,
Expand Down Expand Up @@ -2610,6 +2692,10 @@ impl Converter {
// --- Binder conversion ---

fn convert_binder(&mut self, binder: &cst::Binder) -> Binder {
stacker::maybe_grow(32 * 1024, 2 * 1024 * 1024, || self.convert_binder_impl(binder))
}

fn convert_binder_impl(&mut self, binder: &cst::Binder) -> Binder {
match binder {
cst::Binder::Wildcard { span } => Binder::Wildcard { span: *span },
cst::Binder::Var { span, name } => Binder::Var {
Expand Down
9 changes: 7 additions & 2 deletions src/build/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -663,6 +663,11 @@ impl ModuleCache {
self.entries.get(module_name).map(|c| c.imports())
}

/// Get the cache directory path, if configured.
pub fn cache_dir(&self) -> Option<&Path> {
self.cache_dir.as_deref()
}

/// Get cached exports for a module, loading from disk if needed.
pub fn get_exports(&mut self, module_name: &str) -> Option<&ModuleExports> {
// Check if we need to load from disk first
Expand Down Expand Up @@ -863,7 +868,7 @@ impl ModuleCache {

// ===== File helpers =====

fn module_file_path(cache_dir: &Path, module_name: &str) -> PathBuf {
pub fn module_file_path(cache_dir: &Path, module_name: &str) -> PathBuf {
cache_dir.join("modules").join(format!("{}.bin", module_name))
}

Expand All @@ -884,7 +889,7 @@ fn save_module_file(path: &Path, exports: &ModuleExports) -> io::Result<()> {
Ok(())
}

fn load_module_file(path: &Path) -> io::Result<ModuleExports> {
pub fn load_module_file(path: &Path) -> io::Result<ModuleExports> {
let file = std::fs::File::open(path)?;
let decoder = io::BufReader::new(zstd::Decoder::new(file)
.map_err(|e| io::Error::new(io::ErrorKind::Other, format!("zstd: {e}")))?);
Expand Down
Loading
Loading