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
1 change: 1 addition & 0 deletions crates/sema/src/builtins/members.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ pub(crate) fn native_members<'gcx>(gcx: Gcx<'gcx>, ty: Ty<'gcx>) -> MemberList<'
TyKind::Tuple(_tys) => Default::default(),
TyKind::Mapping(..) => Default::default(),
TyKind::FnPtr(f) => function(gcx, f),
TyKind::Function(_) => Default::default(), // Regular functions don't have direct members
TyKind::Contract(id) => contract(gcx, id),
TyKind::Struct(_id) => expected_ref(),
TyKind::Enum(_id) => Default::default(),
Expand Down
2 changes: 2 additions & 0 deletions crates/sema/src/ty/print.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ impl<'gcx, W: fmt::Write> TyAbiPrinter<'gcx, W> {
TyKind::Elementary(ty) => ty.write_abi_str(&mut self.buf),
TyKind::Contract(_) => self.buf.write_str("address"),
TyKind::FnPtr(_) => self.buf.write_str("function"),
TyKind::Function(_) => self.buf.write_str("function"),
TyKind::Struct(id) => match self.mode {
TyAbiPrinterMode::Signature => {
if self.gcx.struct_recursiveness(id).is_recursive() {
Expand Down Expand Up @@ -282,6 +283,7 @@ impl<'gcx, W: fmt::Write> TySolcPrinter<'gcx, W> {
TyKind::FnPtr(f) => {
self.print_function(None, f.parameters, f.returns, f.state_mutability, f.visibility)
}
TyKind::Function(_) => self.buf.write_str("function"),
TyKind::Struct(id) => {
write!(self.buf, "struct {}", self.gcx.item_canonical_name(id))
}
Expand Down
72 changes: 71 additions & 1 deletion crates/sema/src/ty/ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::{builtins::Builtin, hir};
use alloy_primitives::U256;
use solar_ast::{DataLocation, ElementaryType, StateMutability, TypeSize, Visibility};
use solar_data_structures::{Interned, fmt};
use solar_interface::diagnostics::ErrorGuaranteed;
use solar_interface::{Ident, diagnostics::ErrorGuaranteed};
use std::{borrow::Borrow, hash::Hash, ops::ControlFlow};

/// An interned type.
Expand Down Expand Up @@ -247,11 +247,76 @@ impl<'gcx> Ty<'gcx> {
pub fn parameters(self) -> Option<&'gcx [Self]> {
Some(match self.kind {
TyKind::FnPtr(f) => f.parameters,
TyKind::Function(_) => return None,
TyKind::Event(tys, _) | TyKind::Error(tys, _) => tys,
_ => return None,
})
}

/// Returns the named parameters of the type (parameter names with their types).
/// Returns None if the type doesn't have named parameters or if parameter names cannot be
/// retrieved.
pub fn named_parameters(self, gcx: Gcx<'gcx>) -> Option<Vec<(Ident, Self)>> {
match self.kind {
TyKind::Event(_, event_id) => {
let event = gcx.hir.event(event_id);
let param_types = self.parameters()?;
let mut named_params = Vec::new();

for (&param_id, &param_ty) in event.parameters.iter().zip(param_types.iter()) {
let param_var = gcx.hir.variable(param_id);
let Some(param_name) = param_var.name else {
// If any parameter is unnamed, we can't support named arguments
return None;
};
named_params.push((param_name, param_ty));
}
Some(named_params)
}
TyKind::Error(_, error_id) => {
let error = gcx.hir.error(error_id);
let param_types = self.parameters()?;
let mut named_params = Vec::new();

for (&param_id, &param_ty) in error.parameters.iter().zip(param_types.iter()) {
let param_var = gcx.hir.variable(param_id);
let Some(param_name) = param_var.name else {
// If any parameter is unnamed, we can't support named arguments
return None;
};
named_params.push((param_name, param_ty));
}
Some(named_params)
}
TyKind::Function(function_id) => {
let function = gcx.hir.function(function_id);
if let Some(param_types) =
gcx.item_parameter_types_opt(hir::ItemId::Function(function_id))
{
let mut named_params = Vec::new();
for (&param_id, &param_ty) in function.parameters.iter().zip(param_types.iter())
{
let param_var = gcx.hir.variable(param_id);
let Some(param_name) = param_var.name else {
// If any parameter is unnamed, we can't support named arguments
return None;
};
named_params.push((param_name, param_ty));
}
Some(named_params)
} else {
None
}
}
TyKind::FnPtr(_) => {
// Function pointers don't store parameter names, so we can't provide named
// parameters. Only unnamed arguments are supported for function pointers.
None
}
_ => None,
}
}

/// Returns the return types of the type.
#[inline]
pub fn returns(self) -> Option<&'gcx [Self]> {
Expand Down Expand Up @@ -288,6 +353,7 @@ impl<'gcx> Ty<'gcx> {
| TyKind::IntLiteral(..)
| TyKind::Contract(_)
| TyKind::FnPtr(_)
| TyKind::Function(_)
| TyKind::Enum(_)
| TyKind::Module(_)
| TyKind::BuiltinModule(_)
Expand Down Expand Up @@ -705,6 +771,9 @@ pub enum TyKind<'gcx> {
/// Function pointer: `function(...) returns (...)`.
FnPtr(&'gcx TyFnPtr<'gcx>),

/// Regular function reference.
Function(hir::FunctionId),

/// Contract.
Contract(hir::ContractId),

Expand Down Expand Up @@ -784,6 +853,7 @@ impl TyFlags {
| TyKind::IntLiteral(..)
| TyKind::Contract(_)
| TyKind::FnPtr(_)
| TyKind::Function(_)
| TyKind::Enum(_)
| TyKind::Module(_)
| TyKind::BuiltinModule(_) => {}
Expand Down
205 changes: 191 additions & 14 deletions crates/sema/src/typeck/checker.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use crate::{
builtins::Builtin,
hir::{self, Visit},
ty::{Gcx, Ty, TyKind},
ty::{Gcx, Ty, TyFnPtr, TyKind},
};
use alloy_primitives::U256;
use solar_ast::{DataLocation, ElementaryType, Span};
use solar_data_structures::{Never, map::FxHashMap, pluralize, smallvec::SmallVec};
use solar_interface::{diagnostics::DiagCtxt, sym};
use solar_interface::{Ident, diagnostics::DiagCtxt, sym};
use std::ops::ControlFlow;

pub(super) fn check(gcx: Gcx<'_>, source: hir::SourceId) {
Expand Down Expand Up @@ -90,12 +90,6 @@ impl<'gcx> TypeChecker<'gcx> {
expr: &'gcx hir::Expr<'gcx>,
expected: Option<Ty<'gcx>>,
) -> Ty<'gcx> {
macro_rules! todo {
() => {{
let msg = format!("not yet implemented: {expr:?}");
return self.gcx.mk_ty_err(self.dcx().err(msg).span(expr.span).emit());
}};
}
match expr.kind {
hir::ExprKind::Array(exprs) => {
let mut common = expected.and_then(|arr| arr.base_type(self.gcx));
Expand Down Expand Up @@ -159,14 +153,22 @@ impl<'gcx> TypeChecker<'gcx> {
// TODO: `array.push() = x;` is the only valid call lvalue
let is_array_push = false;
let ty = match callee_ty.kind {
TyKind::FnPtr(_f) => {
// dbg!(callee_ty);
todo!()
TyKind::FnPtr(f) => {
let _ = self.check_function_call_args(args, f, expr.span);
callee_ty
}
TyKind::Function(function_id) => {
let _ = self.check_call_args(args, function_id.into(), "function");
callee_ty
}
TyKind::Type(to) => self.check_explicit_cast(expr.span, to, args),
TyKind::Event(..) | TyKind::Error(..) => {
// return callee_ty
todo!()
TyKind::Event(_, event_id) => {
let _ = self.check_call_args(args, event_id.into(), "event");
callee_ty
}
TyKind::Error(_, error_id) => {
let _ = self.check_call_args(args, error_id.into(), "error");
callee_ty
}
TyKind::Err(_) => callee_ty,
_ => {
Expand Down Expand Up @@ -757,6 +759,176 @@ impl<'gcx> TypeChecker<'gcx> {
.emit();
}
}

/// Helper to get named parameters for a function-like item (function, event, error).
/// Returns None if the item has no named parameters or if parameter names cannot be retrieved.
fn get_named_parameters(&self, item_id: hir::ItemId) -> Option<Vec<(Ident, Ty<'gcx>)>> {
let item = self.gcx.hir.item(item_id);
let param_ids = item.parameters()?;
let param_types = self.gcx.item_parameter_types_opt(item_id)?;

let mut named_params = Vec::new();
for (&param_id, &param_ty) in param_ids.iter().zip(param_types.iter()) {
let param_var = self.gcx.hir.variable(param_id);
let Some(param_name) = param_var.name else {
// If any parameter is unnamed, we can't support named arguments
return None;
};
named_params.push((param_name, param_ty));
}
Some(named_params)
}

/// Validates and type-checks arguments against function-like item parameters.
/// Enforces the constraint that arguments must be ALL unnamed or ALL named.
fn check_call_args(
&mut self,
args: &'gcx hir::CallArgs<'gcx>,
item_id: hir::ItemId,
item_name: &str, // "function", "event", "error", etc.
) -> Result<(), ()> {
self.check_call_args_with_span(args, item_id, item_name, args.span)
}

/// Validates and type-checks arguments for function pointer calls.
/// Function pointers only support unnamed arguments since they don't have parameter names.
fn check_function_call_args(
&mut self,
args: &'gcx hir::CallArgs<'gcx>,
fn_ptr: &TyFnPtr<'gcx>,
error_span: Span,
) -> Result<(), ()> {
match args.kind {
hir::CallArgsKind::Unnamed(arg_exprs) => {
// Validate argument count
if arg_exprs.len() != fn_ptr.parameters.len() {
self.dcx()
.err(format!(
"wrong number of arguments for function: expected {}, found {}",
fn_ptr.parameters.len(),
arg_exprs.len()
))
.span(error_span)
.emit();
return Err(());
}

// Type check each argument sequentially
for (arg_expr, &expected_ty) in arg_exprs.iter().zip(fn_ptr.parameters.iter()) {
let actual_ty = self.check_expr_kind(arg_expr, Some(expected_ty));
self.check_expected(arg_expr, actual_ty, expected_ty);
}
Ok(())
}
hir::CallArgsKind::Named(_) => {
self.dcx()
.err("function pointers do not support named arguments")
.span(error_span)
.emit();
Err(())
}
}
}

/// Validates and type-checks arguments against function-like item parameters with custom span.
/// Enforces the constraint that arguments must be ALL unnamed or ALL named.
fn check_call_args_with_span(
&mut self,
args: &'gcx hir::CallArgs<'gcx>,
item_id: hir::ItemId,
item_name: &str,
error_span: Span,
) -> Result<(), ()> {
let Some(param_types) = self.gcx.item_parameter_types_opt(item_id) else {
return Ok(()); // Item doesn't have parameters we can check
};

match args.kind {
hir::CallArgsKind::Unnamed(arg_exprs) => {
// Validate argument count
if arg_exprs.len() != param_types.len() {
self.dcx()
.err(format!(
"wrong number of arguments for {}: expected {}, found {}",
item_name,
param_types.len(),
arg_exprs.len()
))
.span(error_span)
.emit();
return Err(());
}

// Type check each argument sequentially
for (arg_expr, &expected_ty) in arg_exprs.iter().zip(param_types.iter()) {
let actual_ty = self.check_expr_kind(arg_expr, Some(expected_ty));
self.check_expected(arg_expr, actual_ty, expected_ty);
}
Ok(())
}
hir::CallArgsKind::Named(named_args) => {
// Get named parameters for this item
let Some(named_params) = self.get_named_parameters(item_id) else {
self.dcx()
.err(format!(
"{item_name} does not support named arguments (parameters are not named)"
))
.span(error_span)
.emit();
return Err(());
};

// Validate argument count
if named_args.len() != named_params.len() {
self.dcx()
.err(format!(
"wrong number of arguments for {}: expected {}, found {}",
item_name,
named_params.len(),
named_args.len()
))
.span(error_span)
.emit();
return Err(());
}

// Check that each named argument matches a parameter
for named_arg in named_args {
if let Some((_, expected_ty)) = named_params
.iter()
.find(|(param_name, _)| param_name.name == named_arg.name.name)
{
let actual_ty = self.check_expr_kind(&named_arg.value, Some(*expected_ty));
self.check_expected(&named_arg.value, actual_ty, *expected_ty);
} else {
self.dcx()
.err(format!(
"no parameter named `{}` in {}",
named_arg.name.name, item_name
))
.span(named_arg.name.span)
.emit();
return Err(());
}
}

// Check that all parameters have corresponding arguments
for (param_name, _) in &named_params {
if !named_args.iter().any(|arg| arg.name.name == param_name.name) {
self.dcx()
.err(format!(
"missing argument for parameter `{}` in {}",
param_name.name, item_name
))
.span(error_span)
.emit();
return Err(());
}
}
Ok(())
}
}
}
}

impl<'gcx> hir::Visit<'gcx> for TypeChecker<'gcx> {
Expand Down Expand Up @@ -1074,6 +1246,11 @@ fn binop_common_type<'gcx>(
None
}

TyKind::Function(_) => {
// TODO: Compare regular functions
None
}

TyKind::Elementary(hir::ElementaryType::String)
| TyKind::Elementary(hir::ElementaryType::Bytes)
| TyKind::Elementary(hir::ElementaryType::Fixed(..))
Expand Down
1 change: 1 addition & 0 deletions crates/sema/src/typeck/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ fn ty_storage_size_upper_bound(ty: Ty<'_>, gcx: Gcx<'_>) -> Option<U256> {
| TyKind::Udvt(..)
| TyKind::Enum(..)
| TyKind::FnPtr(..)
| TyKind::Function(_)
| TyKind::DynArray(..) => Some(U256::from(1)),
TyKind::Ref(ty, _) => ty_storage_size_upper_bound(ty, gcx),
TyKind::Array(ty, uint) => {
Expand Down
Loading