diff --git a/kclvm/runtime/src/value/api.rs b/kclvm/runtime/src/value/api.rs index edf488cd9..5d211a320 100644 --- a/kclvm/runtime/src/value/api.rs +++ b/kclvm/runtime/src/value/api.rs @@ -2893,8 +2893,8 @@ pub unsafe extern "C" fn kclvm_builtin_str_replace( ) -> *const kclvm_value_ref_t { let args = ptr_as_ref(args); if let Some(val) = args.pop_arg_first() { - let old = args.arg_i(0).unwrap(); - let new = args.arg_i(1).unwrap(); + let old = args.arg_i(0).expect("expect 1 argument, found 0"); + let new = args.arg_i(1).expect("expect 2 arguments, found 1"); let count = args.arg_i(2); val.str_replace(&old, &new, count.as_ref()).into_raw() } else { @@ -2913,7 +2913,7 @@ pub unsafe extern "C" fn kclvm_builtin_str_removeprefix( ) -> *const kclvm_value_ref_t { let args = ptr_as_ref(args); if let Some(val) = args.pop_arg_first() { - let prefix = args.arg_i(0).unwrap(); + let prefix = args.arg_i(0).expect("expect 1 argument, found 0"); val.str_removeprefix(&prefix).into_raw() } else { panic!("invalid self value in str_removeprefix"); @@ -2931,7 +2931,7 @@ pub unsafe extern "C" fn kclvm_builtin_str_removesuffix( ) -> *const kclvm_value_ref_t { let args = ptr_as_ref(args); if let Some(val) = args.pop_arg_first() { - let suffix = args.arg_i(0).unwrap(); + let suffix = args.arg_i(0).expect("expect 1 argument, found 0"); val.str_removesuffix(&suffix).into_raw() } else { panic!("invalid self value in str_removesuffix"); diff --git a/kclvm/sema/src/builtin/mod.rs b/kclvm/sema/src/builtin/mod.rs index 4d955bc0c..823708ebb 100644 --- a/kclvm/sema/src/builtin/mod.rs +++ b/kclvm/sema/src/builtin/mod.rs @@ -87,7 +87,7 @@ register_builtin! { end: string appended after the last value, default a newline. "#, true, - Some(0), + None, ) multiplyof => Type::function( None, @@ -105,8 +105,8 @@ register_builtin! { }, ], "Check if the modular result of a and b is 0.", - true, - Some(0), + false, + None, ) isunique => Type::function( None, @@ -501,7 +501,7 @@ register_builtin! { }, ], r#"Return the type of the object"#, - true, + false, None, ) } diff --git a/kclvm/sema/src/builtin/string.rs b/kclvm/sema/src/builtin/string.rs index 2e636abeb..fc6cda529 100644 --- a/kclvm/sema/src/builtin/string.rs +++ b/kclvm/sema/src/builtin/string.rs @@ -221,7 +221,7 @@ register_string_member! { Parameter { name: "chars".to_string(), ty: Type::str_ref(), - has_default: false, + has_default: true, }, ], r#"Return a copy of the string with leading characters removed. The chars argument is a string specifying the set of characters to be removed. If omitted or None, the chars argument defaults to removing whitespace. The chars argument is not a prefix; rather, all combinations of its values are stripped:"#, @@ -235,7 +235,7 @@ register_string_member! { Parameter { name: "chars".to_string(), ty: Type::str_ref(), - has_default: false, + has_default: true, }, ], r#"Return a copy of the string with trailing characters removed. The chars argument is a string specifying the set of characters to be removed. If omitted or None, the chars argument defaults to removing whitespace. The chars argument is not a suffix; rather, all combinations of its values are stripped:"#, @@ -425,7 +425,7 @@ register_string_member! { Parameter { name: "chars".to_string(), ty: Type::str_ref(), - has_default: false, + has_default: true, }, ], r#"Return a copy of the string with the leading and trailing characters removed. The chars argument is a string specifying the set of characters to be removed. If omitted or None, the chars argument defaults to removing whitespace. The chars argument is not a prefix or suffix; rather, all combinations of its values are stripped:"#, diff --git a/kclvm/sema/src/resolver/arg.rs b/kclvm/sema/src/resolver/arg.rs index 5a6492e63..2ea62dfd2 100644 --- a/kclvm/sema/src/resolver/arg.rs +++ b/kclvm/sema/src/resolver/arg.rs @@ -26,12 +26,12 @@ impl<'ctx> Resolver<'ctx> { /// Do schema/function/decorator argument type check. pub fn do_arguments_type_check( &mut self, - func: &ast::Expr, + func: &ast::NodeRef, args: &'ctx [ast::NodeRef], kwargs: &'ctx [ast::NodeRef], func_ty: &FunctionType, ) { - let func_name = self.get_func_name(func); + let func_name = self.get_func_name(&func.node); let arg_types = self.exprs(args); let mut kwarg_types: Vec<(String, Rc)> = vec![]; let mut check_table: IndexSet = IndexSet::default(); @@ -52,6 +52,30 @@ impl<'ctx> Resolver<'ctx> { .add_compile_error("missing argument", kw.get_span_pos()); } } + // Do few argument count check + if !func_ty.is_variadic { + let mut got_count = 0; + let mut expect_count = 0; + for param in &func_ty.params { + if !param.has_default { + expect_count += 1; + if check_table.contains(¶m.name) { + got_count += 1 + } + } + } + got_count += args.len(); + if got_count < expect_count { + self.handler.add_compile_error( + &format!( + "expected {} positional argument, found {}", + expect_count, got_count + ), + func.get_span_pos(), + ); + } + } + // Do normal argument type check for (i, ty) in arg_types.iter().enumerate() { let expected_ty = match func_ty.params.get(i) { Some(param) => param.ty.clone(), @@ -72,6 +96,7 @@ impl<'ctx> Resolver<'ctx> { }; self.must_assignable_to(ty.clone(), expected_ty, args[i].get_span_pos(), None) } + // Do keyword argument type check for (i, (arg_name, kwarg_ty)) in kwarg_types.iter().enumerate() { if !func_ty .params diff --git a/kclvm/sema/src/resolver/node.rs b/kclvm/sema/src/resolver/node.rs index 7c2d7565f..555a90fe1 100644 --- a/kclvm/sema/src/resolver/node.rs +++ b/kclvm/sema/src/resolver/node.rs @@ -530,7 +530,7 @@ impl<'ctx> MutSelfTypedResultWalker<'ctx> for Resolver<'ctx> { let range = call_expr.func.get_span_pos(); if call_ty.is_any() { self.do_arguments_type_check( - &call_expr.func.node, + &call_expr.func, &call_expr.args, &call_expr.keywords, &FunctionType::variadic_func(), @@ -538,7 +538,7 @@ impl<'ctx> MutSelfTypedResultWalker<'ctx> for Resolver<'ctx> { self.any_ty() } else if let TypeKind::Function(func_ty) = &call_ty.kind { self.do_arguments_type_check( - &call_expr.func.node, + &call_expr.func, &call_expr.args, &call_expr.keywords, &func_ty, @@ -553,7 +553,7 @@ impl<'ctx> MutSelfTypedResultWalker<'ctx> for Resolver<'ctx> { self.any_ty() } else { self.do_arguments_type_check( - &call_expr.func.node, + &call_expr.func, &call_expr.args, &call_expr.keywords, &schema_ty.func, @@ -887,8 +887,10 @@ impl<'ctx> MutSelfTypedResultWalker<'ctx> for Resolver<'ctx> { ); } } else { - let func = ast::Expr::Identifier(schema_expr.name.node.clone()); - + let func = Box::new(ast::Node::node_with_pos( + ast::Expr::Identifier(schema_expr.name.node.clone()), + schema_expr.name.pos(), + )); self.do_arguments_type_check( &func, &schema_expr.args, diff --git a/kclvm/sema/src/resolver/schema.rs b/kclvm/sema/src/resolver/schema.rs index 61accce1b..ae8050c5b 100644 --- a/kclvm/sema/src/resolver/schema.rs +++ b/kclvm/sema/src/resolver/schema.rs @@ -180,7 +180,7 @@ impl<'ctx> Resolver<'ctx> { Some(ty) => match &ty.kind { TypeKind::Function(func_ty) => { self.do_arguments_type_check( - &decorator.node.func.node, + &decorator.node.func, &decorator.node.args, &decorator.node.keywords, &func_ty, diff --git a/test/grammar/types/args/call_expr_err_too_few_args_0/main.k b/test/grammar/types/args/call_expr_err_too_few_args_0/main.k new file mode 100644 index 000000000..4cd8dcec2 --- /dev/null +++ b/test/grammar/types/args/call_expr_err_too_few_args_0/main.k @@ -0,0 +1 @@ +a = "".startswith() diff --git a/test/grammar/types/args/call_expr_err_too_few_args_0/stderr.golden.py b/test/grammar/types/args/call_expr_err_too_few_args_0/stderr.golden.py new file mode 100644 index 000000000..7a4521194 --- /dev/null +++ b/test/grammar/types/args/call_expr_err_too_few_args_0/stderr.golden.py @@ -0,0 +1,20 @@ +import sys +import kclvm.kcl.error as kcl_error +import os + +cwd = os.path.dirname(os.path.realpath(__file__)) + +kcl_error.print_kcl_error_message( + kcl_error.get_exception( + err_type=kcl_error.ErrType.TypeError_Compile_TYPE, + file_msgs=[ + kcl_error.ErrFileMsg( + filename=cwd + "/main.k", + line_no=1, + col_no=5, + ) + ], + arg_msg='expected 1 argument, found 0' + ), + file=sys.stdout +) diff --git a/test/grammar/types/args/call_expr_err_too_few_args_1/main.k b/test/grammar/types/args/call_expr_err_too_few_args_1/main.k new file mode 100644 index 000000000..0bfc01a41 --- /dev/null +++ b/test/grammar/types/args/call_expr_err_too_few_args_1/main.k @@ -0,0 +1 @@ +a = "".replace("old") diff --git a/test/grammar/types/args/call_expr_err_too_few_args_1/stderr.golden.py b/test/grammar/types/args/call_expr_err_too_few_args_1/stderr.golden.py new file mode 100644 index 000000000..7a4521194 --- /dev/null +++ b/test/grammar/types/args/call_expr_err_too_few_args_1/stderr.golden.py @@ -0,0 +1,20 @@ +import sys +import kclvm.kcl.error as kcl_error +import os + +cwd = os.path.dirname(os.path.realpath(__file__)) + +kcl_error.print_kcl_error_message( + kcl_error.get_exception( + err_type=kcl_error.ErrType.TypeError_Compile_TYPE, + file_msgs=[ + kcl_error.ErrFileMsg( + filename=cwd + "/main.k", + line_no=1, + col_no=5, + ) + ], + arg_msg='expected 1 argument, found 0' + ), + file=sys.stdout +)