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
6 changes: 2 additions & 4 deletions compiler/crates/hy-check/src/buildable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1735,14 +1735,12 @@ impl<'a> Collector<'a> {
OpKind::Chan(Box::new(self.kind_of_ty_with_subst(elem, subst, depth + 1)))
}
TypeExpr::Option { .. } => OpKind::Unknown,
TypeExpr::Fn { results, .. } => OpKind::Fn(
TypeExpr::Fn { params, results, .. } => OpKind::Fn(
results
.iter()
.map(|r| self.kind_of_ty_with_subst(r, subst, depth + 1))
.collect(),
// A bare fn TYPE does not carry a call signature the kind model
// can trust a count from — keep the arity unknown.
None,
Some(params.len()),
),
}
}
Expand Down
36 changes: 30 additions & 6 deletions compiler/crates/hy-check/src/emit_guard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -640,8 +640,12 @@ impl<'a> Checker<'a> {
decl.params.iter().map(|p| (p.name.clone(), p.ty.clone())).collect()
})
} else if let Some(b) = self.lookup_binding(st, name) {
if let Some(TypeExpr::Fn { params, .. }) = &b.ty {
Some(params.iter().map(|p| (p.name.clone(), p.ty.clone())).collect())
if let Some(ty) = &b.ty {
if let TypeExpr::Fn { params, .. } = self.resolve_type_alias(ty) {
Some(params.iter().map(|p| (p.name.clone(), p.ty.clone())).collect())
} else {
None
}
} else {
None
}
Expand Down Expand Up @@ -678,15 +682,30 @@ impl<'a> Checker<'a> {
},
_ => None,
};
let decl = if let Some(s) = recv_struct {
self.method_decls.get(&(s, method_name.clone())).copied()
let decl = if let Some(ref s) = recv_struct {
self.method_decls.get(&(s.clone(), method_name.clone())).copied()
} else {
self.method_decls
.iter()
.find(|((_, m), _)| m == method_name)
.map(|(_, d)| *d)
};
decl.map(|d| d.params.iter().map(|p| (p.name.clone(), p.ty.clone())).collect())
if let Some(d) = decl {
Some(d.params.iter().map(|p| (p.name.clone(), p.ty.clone())).collect())
} else {
recv_struct
.and_then(|s| self.structs.get(&s))
.and_then(|fs| fs.iter().find(|(n, _)| n == method_name))
.and_then(|(_, fty)| match self.resolve_type_alias(fty) {
TypeExpr::Fn { params, .. } => Some(
params
.iter()
.map(|p| (p.name.clone(), p.ty.clone()))
.collect(),
),
_ => None,
})
}
}
}
Expr::FnLit { params, .. } => {
Expand All @@ -707,8 +726,13 @@ impl<'a> Checker<'a> {
};
if let Some((p_name, p_ty)) = target {
let resolved_ty = self.resolve_type_alias(p_ty);
let label = if p_name.is_empty() {
format!("parameter #{}", i + 1)
} else {
format!("parameter `{}`", p_name)
};
self.check_const_overflow_init(
&format!("parameter `{}`", p_name),
&label,
&resolved_ty,
&a.value,
None,
Expand Down
56 changes: 47 additions & 9 deletions compiler/crates/hy-check/src/loans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2084,10 +2084,17 @@ impl<'a> Checker<'a> {
match unwrap_parens(callee) {
Expr::Ident { name, .. } => {
if let Some(b) = self.lookup_binding(st, name) {
if let Some(hy_syntax::TypeExpr::Fn { params, .. }) = &b.ty {
return params
.get(idx)
.is_some_and(|p| matches!(p.ty, hy_syntax::TypeExpr::Pointer { .. }));
if let Some(ty) = &b.ty {
if let hy_syntax::TypeExpr::Fn { params, .. } = self.resolve_type_alias(ty) {
return params
.get(idx)
.is_some_and(|p| {
matches!(
self.resolve_type_alias(&p.ty),
hy_syntax::TypeExpr::Pointer { .. }
)
});
}
}
return false;
}
Expand Down Expand Up @@ -2127,10 +2134,28 @@ impl<'a> Checker<'a> {
},
_ => None,
});
if let Some(f) = recv_base
.as_ref()
.and_then(|rb| self.method_decls.get(&(rb.clone(), head.to_string())))
{
return f
.params
.get(idx)
.is_some_and(|p| matches!(p.ty, hy_syntax::TypeExpr::Pointer { .. }));
}
recv_base
.and_then(|rb| self.method_decls.get(&(rb, head.to_string())))
.and_then(|f| f.params.get(idx))
.is_some_and(|p| matches!(p.ty, hy_syntax::TypeExpr::Pointer { .. }))
.and_then(|rb| self.structs.get(&rb))
.and_then(|fs| fs.iter().find(|(n, _)| n == name))
.and_then(|(_, fty)| match self.resolve_type_alias(fty) {
hy_syntax::TypeExpr::Fn { params, .. } => params.get(idx).map(|p| {
matches!(
self.resolve_type_alias(&p.ty),
hy_syntax::TypeExpr::Pointer { .. }
)
}),
_ => None,
})
.unwrap_or(false)
}
Expr::FnLit { params, .. } => params
.get(idx)
Expand Down Expand Up @@ -6570,7 +6595,9 @@ impl<'a> Checker<'a> {
None => (false, true),
}
} else if let Some(b) = self.lookup_binding(st, name) {
if let Some(hy_syntax::TypeExpr::Fn { params, .. }) = &b.ty {
if let Some(hy_syntax::TypeExpr::Fn { params, .. }) =
b.ty.as_ref().map(|t| self.resolve_type_alias(t))
{
match params.get(i) {
Some(p) => {
let is_ptr = matches!(p.ty, hy_syntax::TypeExpr::Pointer { .. })
Expand Down Expand Up @@ -6608,7 +6635,18 @@ impl<'a> Checker<'a> {
};
(false, mutates)
} else if matches!(self.shape_of_expr(st, callee), TypeShape::Closure) {
(false, true)
let is_ptr = self
.ty_of_expr(st, callee)
.map(|t| self.resolve_type_alias(&t))
.and_then(|t| match t {
hy_syntax::TypeExpr::Fn { params, .. } => params.get(i).map(|p| {
matches!(p.ty, hy_syntax::TypeExpr::Pointer { .. })
|| matches!(self.shape_of_type(&p.ty), TypeShape::Pointer(_))
}),
_ => None,
})
.unwrap_or(false);
(is_ptr, true)
} else {
let recv_base = match self.shape_of_expr(st, base) {
TypeShape::Struct(Some(s)) => Some(s),
Expand Down
31 changes: 31 additions & 0 deletions compiler/crates/hy-check/tests/call_arity_and_return_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,3 +308,34 @@ fn call_argument_types_stay_accepted() {
);
}

#[test]
fn fn_type_local_and_struct_field_soundness_issue_727() {
assert_ok(
"package main\n\nimport \"fmt\"\n\nfn main() {\n var cb fn() int\n fmt.println(cb == nil)\n}\n",
);
assert_ok("package main\n\nfn main() {\n var f fn(int) int\n _ = f(1)\n}\n");
assert_ok(
"package main\n\nstruct handler {\n on_event fn(int) bool\n name string\n}\n\nfn main() {\n h := handler{name: \"click\"}\n _ = h.name\n}\n",
);
assert_code(
"package main\n\nfn main() {\n var f fn(int) int\n _ = f(1, 2)\n}\n",
"HYB-E1002",
);
assert_code(
"package main\n\nfn main() {\n var f fn(u8) int\n _ = f(999)\n}\n",
"HYB-E6002",
);
assert_code(
"package main\n\ntype cb = fn(u8) int\n\nfn main() {\n var f cb\n _ = f(999)\n}\n",
"HYB-E6002",
);
assert_code(
"package main\n\nstruct handler {\n on_event fn(u8) bool\n}\n\nfn main() {\n var h handler\n _ = h.on_event(999)\n}\n",
"HYB-E6002",
);
assert!(
codes("package main\n\nfn main() {\n var f fn([no_such_const]int) int\n _ = f\n}\n")
.contains(&"HYB-E1001".to_string())
);
}

28 changes: 22 additions & 6 deletions compiler/crates/hy-emit/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1737,20 +1737,35 @@ impl Emitter {
TypeExpr::Fn {
params, results, ..
} => {
let all_named = params.iter().all(|p| {
!p.name.is_empty()
&& !matches!(&p.ty, TypeExpr::Named { name, .. } if name.is_empty())
});
let ps = params
.iter()
.map(|p| format!("{} {}", p.name, self.type_text(&p.ty)))
.map(|p| {
let t = self.type_text(&p.ty);
if p.name.is_empty() {
t
} else if t.is_empty() {
self.go_type_name(&p.name)
} else if all_named {
format!("{} {}", p.name, t)
} else {
t
}
})
.collect::<Vec<_>>()
.join(", ");
let rs = results
.iter()
.map(|t| self.type_text(t))
.collect::<Vec<_>>()
.join(", ");
if rs.is_empty() {
format!("func({})", ps)
} else {
format!("func({}) {}", ps, rs)
match results.len() {
0 => format!("func({})", ps),
1 => format!("func({}) {}", ps, rs),
_ => format!("func({}) ({})", ps, rs),
}
}
}
Expand Down Expand Up @@ -1910,7 +1925,8 @@ impl Emitter {
TypeExpr::Slice { .. }
| TypeExpr::Map { .. }
| TypeExpr::Chan { .. }
| TypeExpr::Pointer { .. } => "nil".into(),
| TypeExpr::Pointer { .. }
| TypeExpr::Fn { .. } => "nil".into(),
TypeExpr::Option { .. } => format!("{}{{}}", self.type_text(t)),
_ => format!("*new({})", self.type_text(t)),
}
Expand Down
47 changes: 46 additions & 1 deletion compiler/crates/hy-emit/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2943,7 +2943,6 @@ fn m4_initialism_mapping_for_foreign_go_names_issue_658() {
assert!(go.contains("strings.ToValidUTF8(\"a\", \"b\")"), "expected strings.ToValidUTF8, got:\n{}", go);
}


#[test]
fn package_mode_export_normalization_collision_issue_1421() {
let check_collision = |src: &str, expected_msg_part: &str| {
Expand Down Expand Up @@ -3032,3 +3031,49 @@ fn main() {}
assert!(go6.contains("func read_url() int {"));
assert!(go6.contains("func read_u_r_l() int {"));
}

#[test]
fn fn_type_local_and_struct_field_emit_valid_go_issue_727() {
let src = r#"
package main

import "fmt"

struct handler {
on_event fn(int) bool
name string
}

fn main() {
var cb fn() int
fmt.println(cb == nil)
var f fn(u8) (int, error)
_ = f
var mixed fn(x int, string) bool
_ = mixed
h := handler{on_event: fn(x int) bool { return x > 0 }, name: "click"}
fmt.println(h.name, h.on_event(1))
}
"#;
let go = emit(src);
assert!(
go.contains("var cb func() int\n\tfmt.Println(cb == nil)"),
"var cb fn() int must not absorb following statement:\n{}",
go
);
assert!(
go.contains("var f func(uint8) (int, error)"),
"unnamed primitive param and multi-result fn type must emit valid Go:\n{}",
go
);
assert!(
go.contains("var mixed func(int, string) bool"),
"mixed named and unnamed fn-type params must emit unnamed Go params:\n{}",
go
);
assert!(
go.contains("on_event func(int) bool") && go.contains("name") && go.contains("h.name"),
"struct fields after fn-typed field must be preserved:\n{}",
go
);
}
Loading
Loading