From 73fbbae1d99b0988816dfc1d680cbdab9b9456e2 Mon Sep 17 00:00:00 2001 From: Dylan Socolobsky Date: Sun, 20 Jul 2025 15:49:24 -0300 Subject: [PATCH] Validate that `self` must be the 1st parameter in method definition if present --- src/check/mod.rs | 13 +++++++++++++ src/ir/lowering/errors.rs | 2 ++ src/ir/lowering/functions.rs | 13 +++++++++++++ tests/checks.rs | 15 +++++++++++++++ tests/invalid_programs/invalid_self_arg.con | 7 +++++++ 5 files changed, 50 insertions(+) create mode 100644 tests/invalid_programs/invalid_self_arg.con diff --git a/src/check/mod.rs b/src/check/mod.rs index 3c199a2..240be12 100644 --- a/src/check/mod.rs +++ b/src/check/mod.rs @@ -621,5 +621,18 @@ pub fn lowering_error_to_report(error: LoweringError) -> Report<'static, FileSpa .with_help(format!("Implement the trait {:?} for the passed parameter type.", error.trait_name)); report.finish() } + LoweringError::InvalidSelfArgument { span, path } => { + let path = path.display().to_string(); + let filespan = FileSpan::new(path, span.from..span.to); + Report::build(ReportKind::Error, filespan.clone()) + .with_code("InvalidSelfArgument") + .with_label( + Label::new(filespan) + .with_message("`self` parameter must be the first parameter in method") + .with_color(colors.next()), + ) + .with_message("`self` parameter must be the first parameter in method") + .finish() + } } } diff --git a/src/ir/lowering/errors.rs b/src/ir/lowering/errors.rs index 79e62dd..813a54c 100644 --- a/src/ir/lowering/errors.rs +++ b/src/ir/lowering/errors.rs @@ -139,6 +139,8 @@ pub enum LoweringError { }, #[error("missing variant")] MissingVariant(Box), + #[error("`self` parameter must be the first parameter in method")] + InvalidSelfArgument { span: Span, path: PathBuf }, } #[derive(Debug, Clone)] diff --git a/src/ir/lowering/functions.rs b/src/ir/lowering/functions.rs index c362670..c081056 100644 --- a/src/ir/lowering/functions.rs +++ b/src/ir/lowering/functions.rs @@ -8,6 +8,7 @@ use crate::{ expressions::FnCallOp, functions::{FunctionDecl, FunctionDef}, statements::{self, LetStmtTarget}, + types::TypeDescriptor, }, ir::{ BasicBlock, ConcreteIntrinsic, Function, Local, LocalKind, Operand, Place, Span, @@ -167,6 +168,18 @@ pub(crate) fn lower_func( }); } + // If this is a method then validate that `self` is the first parameter + if method_of.is_some() { + for param in func.decl.params.iter().skip(1) { + if let TypeDescriptor::SelfType { span, .. } = param.r#type { + return Err(LoweringError::InvalidSelfArgument { + span, + path: fn_builder.get_file_path().clone(), + }); + } + } + } + fn_builder.ret_local = fn_builder.body.locals.len(); fn_builder.body.locals.push(Local::new( None, diff --git a/tests/checks.rs b/tests/checks.rs index 8c6057a..d61d1ad 100644 --- a/tests/checks.rs +++ b/tests/checks.rs @@ -258,3 +258,18 @@ fn missing_variant() { error ); } + +#[test] +fn invalid_self_arg() { + let (source, name) = ( + include_str!("invalid_programs/invalid_self_arg.con"), + "invalid_programs/invalid_self_arg.con", + ); + let error = check_invalid_program(source, name); + + assert!( + matches!(&error, LoweringError::InvalidSelfArgument { .. }), + "{:#?}", + error + ); +} diff --git a/tests/invalid_programs/invalid_self_arg.con b/tests/invalid_programs/invalid_self_arg.con new file mode 100644 index 0000000..08fa10a --- /dev/null +++ b/tests/invalid_programs/invalid_self_arg.con @@ -0,0 +1,7 @@ +mod Test { + struct A {} + + impl A { + fn x(a: u32, &self) {} + } +}