Skip to content

Commit

Permalink
Fix issue with matching when the first argument is a generic function…
Browse files Browse the repository at this point in the history
… definition (#962)

* Fix issue with matching when the first argument is a generic function definition

* Make clippy happy
  • Loading branch information
dfellis authored Nov 19, 2024
1 parent 7a79b74 commit f6a5ee6
Show file tree
Hide file tree
Showing 7 changed files with 206 additions and 57 deletions.
14 changes: 14 additions & 0 deletions alan/src/compile/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2698,6 +2698,20 @@ test_full!(generic_in_a_generic => r#"
stdout_js "[ true, false, true ]\n";
stdout_rs "[true, false, true]\n"; // TODO: Make these match
);
test_full!(first_arg_generic_fn => r#"
fn batchCompare{T}(cond: (T, T) -> bool, a: Array{T}, b: Array{T}) {
return a.map(fn (aVal: T) = b.some(fn (bVal: T) = cond(aVal, bVal)));
}
export fn main {
let vals1 = [1, 9, 25];
let vals2 = [1, 3, 5, 7, 9];
batchCompare(eq, vals1, vals2).print;
}"#;
stdout_js "[ true, true, false ]\n";
stdout_rs "[true, true, false]\n";
);
test_compile_error!(invalid_generics => r#"
type box{V} =
set: bool,
Expand Down
58 changes: 41 additions & 17 deletions alan/src/compile/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -391,28 +391,52 @@ pub fn compile(source_file: String) -> Result<(), Box<dyn std::error::Error>> {

/// The `test` function is a thin wrapper on top of `compile` that compiles the specified file in
/// test mode, then immediately invokes it, and deletes the binary when done.
pub fn test(source_file: String) -> Result<(), Box<dyn std::error::Error>> {
Program::set_target_lang_rs();
pub fn test(source_file: String, js: bool) -> Result<(), Box<dyn std::error::Error>> {
if js {
Program::set_target_lang_js();
} else {
Program::set_target_lang_rs();
}
let mut program = Program::get_program();
program
.env
.insert("ALAN_TARGET".to_string(), "test".to_string());
Program::return_program(program);
let binary = build(source_file)?;
let mut run = Command::new(format!("./{}", binary))
.current_dir(current_dir()?)
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()?;
let ecode = run.wait()?;
Command::new("rm")
.current_dir(current_dir()?)
.arg(binary)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()?;
if !ecode.success() {
std::process::exit(ecode.code().unwrap());
if js {
let jsfile = web(source_file)?;
let mut run = Command::new("node")
.current_dir(current_dir()?)
.arg(format!("{}.js", jsfile))
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()?;
let ecode = run.wait()?;
Command::new("rm")
.current_dir(current_dir()?)
.arg(format!("{}.js", jsfile))
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()?;
if !ecode.success() {
std::process::exit(ecode.code().unwrap());
}
} else {
let binary = build(source_file)?;
let mut run = Command::new(format!("./{}", binary))
.current_dir(current_dir()?)
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()?;
let ecode = run.wait()?;
Command::new("rm")
.current_dir(current_dir()?)
.arg(binary)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()?;
if !ecode.success() {
std::process::exit(ecode.code().unwrap());
}
}
Ok(())
}
Expand Down
6 changes: 3 additions & 3 deletions alan/src/lntojs/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ pub fn from_microstatement(
}
}
Ok((
format!("await {}({})", jsname, argstrs.join(", ")).to_string(),
format!("(await {}({}))", jsname, argstrs.join(", ")).to_string(),
out,
deps,
))
Expand Down Expand Up @@ -456,7 +456,7 @@ pub fn from_microstatement(
}
}
Ok((
format!("await {}({})", jsname, argstrs.join(", ")).to_string(),
format!("(await {}({}))", jsname, argstrs.join(", ")).to_string(),
out,
deps,
))
Expand Down Expand Up @@ -1282,7 +1282,7 @@ pub fn from_microstatement(
argstrs.push(a);
}
Ok((
format!("await {}({})", name, argstrs.join(", ")).to_string(),
format!("(await {}({}))", name, argstrs.join(", ")).to_string(),
out,
deps,
))
Expand Down
3 changes: 2 additions & 1 deletion alan/src/lntojs/typen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub fn ctype_to_jtype(
mut deps: OrderedHashMap<String, String>,
) -> Result<(String, OrderedHashMap<String, String>), Box<dyn std::error::Error>> {
match ctype {
CType::Mut(t) => ctype_to_jtype(t, deps),
CType::Void => Ok(("".to_string(), deps)),
CType::Infer(s, _) => Err(format!(
"Inferred type matching {} was not realized before code generation",
Expand Down Expand Up @@ -244,7 +245,7 @@ pub fn ctype_to_jtype(
Ok(("".to_string(), deps))
}
CType::Fail(m) => CType::fail(m),
otherwise => CType::fail(&format!("Lower stage of the compiler received unresolved algebraic type {}, cannot deal with it ehre. Please report this error.", otherwise.to_strict_string(false))),
otherwise => CType::fail(&format!("Lower stage of the compiler received unresolved algebraic type {}, cannot deal with it here. Please report this error.", otherwise.to_functional_string())),
}
}

Expand Down
3 changes: 2 additions & 1 deletion alan/src/lntors/typen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub fn ctype_to_rtype(
mut deps: OrderedHashMap<String, String>,
) -> Result<(String, OrderedHashMap<String, String>), Box<dyn std::error::Error>> {
match ctype {
CType::Mut(t) => ctype_to_rtype(t, in_function_type, deps),
CType::Void => Ok(("void".to_string(), deps)),
CType::Infer(s, _) => Err(format!(
"Inferred type matching {} was not realized before code generation",
Expand Down Expand Up @@ -449,7 +450,7 @@ pub fn ctype_to_rtype(
Ok((format!("Vec<{}>", s), deps))
}
CType::Fail(m) => CType::fail(m),
otherwise => CType::fail(&format!("Lower stage of the compiler received unresolved algebraic type {}, cannot deal with it here. Please report this error.", otherwise.to_strict_string(false))),
otherwise => CType::fail(&format!("Lower stage of the compiler received unresolved algebraic type {}, cannot deal with it here. Please report this error.", otherwise.to_functional_string())),
}
}

Expand Down
9 changes: 8 additions & 1 deletion alan/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ enum Commands {
default_value = "./index.ln"
)]
file: String,
#[arg(
short,
long,
help = "Test via Javascript & Node.js, not natively",
default_value_t = false
)]
js: bool,
},
#[command(about = "Install dependencies for your Alan project")]
Install {
Expand All @@ -84,7 +91,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
match &args.commands {
Some(Commands::Bundle { file }) => Ok(bundle(file.to_string())?),
Some(Commands::Compile { file }) => Ok(compile(file.to_string())?),
Some(Commands::Test { file }) => Ok(test(file.to_string())?),
Some(Commands::Test { file, js }) => Ok(test(file.to_string(), *js)?),
Some(Commands::ToRs { file }) => Ok(to_rs(file.to_string())?),
Some(Commands::ToJs { file }) => Ok(to_js(file.to_string())?),
_ => Err("Command not yet supported".into()),
Expand Down
170 changes: 136 additions & 34 deletions alan/src/program/ctype.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1656,42 +1656,27 @@ impl CType {
}
}
(Some(CType::AnyOf(ts)), Some(b)) => {
// Multiple of these `AnyOf` types may be viable. Accept all that are, and
// later on something should hopefully work as a tiebreaker.
let mut success = false;
for t in ts {
// We need to check each of these and accept the one that passes, or
// fail if none of them pass. It's expected that most of them will
// fail, so we can't just push them onto the queue, as those mismatches
// will fail out of the function. Instead we clone the hashmap and add
// each of these as a singular element to push through, merging the
// hashmap on success and exiting the loop.
let mut generic_types_inner = generic_types.clone();
if CType::infer_generics_inner_loop(
&mut generic_types_inner,
vec![(t, b)],
)
.is_ok()
{
// If there's a conflict between the inferred types, we skip
let mut matches = true;
for (k, v) in &generic_types_inner {
match generic_types.get(k) {
Some(old_v) => {
if old_v != v {
matches = false;
}
}
None => { /* Do nothing */ }
}
}
if !matches {
continue;
}
success = true;
for (k, v) in &generic_types_inner {
generic_types.insert(k.clone(), v.clone());
let inner_results = ts
.iter()
.map(|t| {
let mut generic_types_inner = generic_types.clone();
if CType::infer_generics_inner_loop(
&mut generic_types_inner,
vec![(t, b)],
)
.is_ok()
{
success = true;
} else {
// Reset it on failure, just in case
generic_types_inner = generic_types.clone();
}
}
}
generic_types_inner
})
.collect::<Vec<HashMap<String, CType>>>();
if !success {
return Err(format!(
"None of {} matches {}",
Expand All @@ -1703,6 +1688,123 @@ impl CType {
)
.into());
}
// Merge the results into a singular set to check. If there are multiple
// values for the same key, merge them as an `AnyOf`.
let mut combined_types = HashMap::new();
for gti in inner_results {
for (k, v) in &gti {
match combined_types.get(k) {
None => {
combined_types.insert(k.clone(), v.clone());
}
Some(other_v) => match (other_v, v) {
(CType::AnyOf(ots), nt) => {
let mut preexists = false;
for t in ots {
if t.to_functional_string()
== nt.to_functional_string()
{
preexists = true;
}
}
if !preexists {
let mut nts = ots.clone();
nts.push(nt.clone());
combined_types.insert(k.clone(), CType::AnyOf(nts));
}
}
(ot, nt) => {
combined_types.insert(
k.clone(),
CType::AnyOf(vec![ot.clone(), nt.clone()]),
);
}
},
}
}
}
// Now comparing the combined resolved types with what was in the original
// set, anything new gets included, but we attempt to *narrow* the `AnyOf`
// to as few as possible, when possible
for (k, v) in &combined_types {
match generic_types.get(k) {
None => {
generic_types.insert(k.clone(), v.clone());
}
Some(old_v) => match (old_v, v) {
(CType::AnyOf(oldts), CType::AnyOf(newts)) => {
let mut outts = Vec::new();
for ot in oldts {
for nt in newts {
if ot.to_functional_string()
== nt.to_functional_string()
{
outts.push(nt.clone());
}
}
}
generic_types.insert(k.clone(), CType::AnyOf(outts));
}
(ot, CType::AnyOf(newts)) => {
let mut success = false;
for nt in newts {
if ot.to_functional_string()
== nt.to_functional_string()
{
success = true;
break;
}
}
if !success {
return Err(format!(
"None of {} matches {}",
newts
.iter()
.map(|t| t.to_strict_string(false))
.collect::<Vec<String>>()
.join(" & "),
ot.to_strict_string(false)
)
.into());
}
}
(CType::AnyOf(oldts), nt) => {
let mut success = false;
for ot in oldts {
if ot.to_functional_string()
== nt.to_functional_string()
{
success = true;
break;
}
}
if !success {
return Err(format!(
"None of {} matches {}",
oldts
.iter()
.map(|t| t.to_strict_string(false))
.collect::<Vec<String>>()
.join(" & "),
nt.to_strict_string(false)
)
.into());
}
generic_types.insert(k.clone(), nt.clone());
}
(ot, nt) => {
if ot.to_functional_string() != nt.to_functional_string() {
return Err(format!(
"{} does not match {}",
ot.to_strict_string(false),
nt.to_strict_string(false),
)
.into());
}
}
},
}
}
}
_ => {
return Err(format!("Mismatch between {:?} and {:?}", a, i).into());
Expand Down

0 comments on commit f6a5ee6

Please sign in to comment.