diff --git a/kclvm/sema/src/builtin/string.rs b/kclvm/sema/src/builtin/string.rs index 64ec80594..32903bf52 100644 --- a/kclvm/sema/src/builtin/string.rs +++ b/kclvm/sema/src/builtin/string.rs @@ -2,7 +2,7 @@ use indexmap::IndexMap; use once_cell::sync::Lazy; use std::rc::Rc; -use crate::ty::Type; +use crate::ty::{Parameter, Type}; macro_rules! register_string_member { ($($name:ident => $ty:expr)*) => ( @@ -27,7 +27,23 @@ register_string_member! { count => Type::function( Some(Rc::new(Type::STR)), Rc::new(Type::INT), - &[], + &[ + Parameter { + name: "sub".to_string(), + ty: Type::str_ref(), + has_default: false, + }, + Parameter { + name: "start".to_string(), + ty: Type::int_ref(), + has_default: true, + }, + Parameter { + name: "end".to_string(), + ty: Type::int_ref(), + has_default: true, + }, + ], r#"Return the number of non-overlapping occurrences of substring sub in the range [start, end]. Optional arguments start and end are interpreted as in slice notation."#, false, None, @@ -35,7 +51,23 @@ register_string_member! { endswith => Type::function( Some(Rc::new(Type::STR)), Rc::new(Type::BOOL), - &[], + &[ + Parameter { + name: "val".to_string(), + ty: Type::str_ref(), + has_default: false, + }, + Parameter { + name: "start".to_string(), + ty: Type::int_ref(), + has_default: true, + }, + Parameter { + name: "end".to_string(), + ty: Type::int_ref(), + has_default: true, + }, + ], r#"Return True if the string ends with the specified suffix, otherwise return False. suffix can also be a tuple of suffixes to look for. With optional start, test beginning at that position. With optional end, stop comparing at that position."#, false, None, @@ -43,7 +75,23 @@ register_string_member! { find => Type::function( Some(Rc::new(Type::STR)), Rc::new(Type::INT), - &[], + &[ + Parameter { + name: "sub".to_string(), + ty: Type::str_ref(), + has_default: false, + }, + Parameter { + name: "start".to_string(), + ty: Type::int_ref(), + has_default: true, + }, + Parameter { + name: "end".to_string(), + ty: Type::int_ref(), + has_default: true, + }, + ], r#"Return the lowest index in the string where substring sub is found within the slice s[start:end]. Optional arguments start and end are interpreted as in slice notation. Return -1 if sub is not found."#, false, None, @@ -59,7 +107,23 @@ register_string_member! { index => Type::function( Some(Rc::new(Type::STR)), Rc::new(Type::INT), - &[], + &[ + Parameter { + name: "sub".to_string(), + ty: Type::str_ref(), + has_default: false, + }, + Parameter { + name: "start".to_string(), + ty: Type::int_ref(), + has_default: true, + }, + Parameter { + name: "end".to_string(), + ty: Type::int_ref(), + has_default: true, + }, + ], r#"Like str.find(), but raise an error when the substring is not found."#, false, None, @@ -123,9 +187,15 @@ register_string_member! { join => Type::function( Some(Rc::new(Type::STR)), Rc::new(Type::STR), - &[], + &[ + Parameter { + name: "iter".to_string(), + ty: Type::list_ref(Type::any_ref()), + has_default: false, + }, + ], r#"Return a string which is the concatenation of the strings in iterable. An error will be raised if there are any non-string values in iterable. The separator between elements is the string providing this method."#, - true, + false, None, ) lower => Type::function( @@ -133,7 +203,7 @@ register_string_member! { Rc::new(Type::STR), &[], r#"Return a copy of the string with all the cased characters converted to lowercase."#, - true, + false, None, ) upper => Type::function( @@ -141,93 +211,209 @@ register_string_member! { Rc::new(Type::STR), &[], r#""#, - true, + false, None, ) lstrip => Type::function( Some(Rc::new(Type::STR)), Rc::new(Type::STR), - &[], + &[ + Parameter { + name: "chars".to_string(), + ty: Type::str_ref(), + has_default: false, + }, + ], 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:"#, - true, + false, None, ) rstrip => Type::function( Some(Rc::new(Type::STR)), Rc::new(Type::STR), - &[], + &[ + Parameter { + name: "chars".to_string(), + ty: Type::str_ref(), + has_default: false, + }, + ], 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:"#, - true, + false, None, ) replace => Type::function( Some(Rc::new(Type::STR)), Rc::new(Type::STR), - &[], + &[ + Parameter { + name: "old".to_string(), + ty: Type::str_ref(), + has_default: false, + }, + Parameter { + name: "new".to_string(), + ty: Type::str_ref(), + has_default: false, + }, + Parameter { + name: "count".to_string(), + ty: Type::int_ref(), + has_default: true, + }, + ], r#"Return a copy of the string with all occurrences of substring old replaced by new. If the optional argument count is given, only the first count occurrences are replaced.Return a copy of the string with all occurrences of substring old replaced by new. If the optional argument count is given, only the first count occurrences are replaced."#, - true, + false, None, ) removeprefix => Type::function( Some(Rc::new(Type::STR)), Rc::new(Type::STR), - &[], + &[ + Parameter { + name: "prefix".to_string(), + ty: Type::str_ref(), + has_default: false, + }, + ], r#"If the string starts with the prefix string, return string[len(prefix):]. Otherwise, return a copy of the original string."#, - true, + false, None, ) removesuffix => Type::function( Some(Rc::new(Type::STR)), Rc::new(Type::STR), - &[], + &[ + Parameter { + name: "suffix".to_string(), + ty: Type::str_ref(), + has_default: false, + }, + ], r#"If the string ends with the suffix string and that suffix is not empty, return string[:-len(suffix)]. Otherwise, return a copy of the original string."#, - true, + false, None, ) rfind => Type::function( Some(Rc::new(Type::STR)), Rc::new(Type::INT), - &[], + &[ + Parameter { + name: "sub".to_string(), + ty: Type::str_ref(), + has_default: false, + }, + Parameter { + name: "start".to_string(), + ty: Type::int_ref(), + has_default: true, + }, + Parameter { + name: "end".to_string(), + ty: Type::int_ref(), + has_default: true, + }, + ], r#"Return the highest index in the string where substring sub is found, such that sub is contained within s[start:end]. Optional arguments start and end are interpreted as in slice notation. Return -1 on failure."#, - true, + false, None, ) rindex => Type::function( Some(Rc::new(Type::STR)), Rc::new(Type::INT), - &[], + &[ + Parameter { + name: "sub".to_string(), + ty: Type::str_ref(), + has_default: false, + }, + Parameter { + name: "start".to_string(), + ty: Type::int_ref(), + has_default: true, + }, + Parameter { + name: "end".to_string(), + ty: Type::int_ref(), + has_default: true, + }, + ], r#"Like rfind() but raises ValueError when the substring sub is not found."#, - true, + false, None, ) rsplit => Type::function( Some(Rc::new(Type::STR)), Type::list_ref(Rc::new(Type::STR)), - &[], + &[ + Parameter { + name: "sep".to_string(), + ty: Type::str_ref(), + has_default: true, + }, + Parameter { + name: "maxsplit".to_string(), + ty: Type::int_ref(), + has_default: true, + }, + ], r#"Return a list of the words in the string, using sep as the delimiter string. If maxsplit is given, at most maxsplit splits are done, the rightmost ones. If sep is not specified or None, any whitespace string is a separator. Except for splitting from the right, rsplit() behaves like split() which is described in detail below."#, - true, + false, None, ) split => Type::function( Some(Rc::new(Type::STR)), Type::list_ref(Rc::new(Type::STR)), - &[], + &[ + Parameter { + name: "sep".to_string(), + ty: Type::str_ref(), + has_default: true, + }, + Parameter { + name: "maxsplit".to_string(), + ty: Type::int_ref(), + has_default: true, + }, + ], r#"Return a list of the words in the string, using sep as the delimiter string. If maxsplit is given, at most maxsplit splits are done (thus, the list will have at most maxsplit+1 elements). If maxsplit is not specified or -1, then there is no limit on the number of splits (all possible splits are made)."#, - true, + false, None, ) splitlines => Type::function( Some(Rc::new(Type::STR)), Type::list_ref(Rc::new(Type::STR)), - &[], + &[ + Parameter { + name: "keepends".to_string(), + ty: Type::bool_ref(), + has_default: true, + }, + ], r#"Return a list of the lines in the string, breaking at line boundaries. Line breaks are not included in the resulting list unless keepends is given and true."#, - true, + false, None, ) startswith => Type::function( Some(Rc::new(Type::STR)), Rc::new(Type::BOOL), - &[], + &[ + Parameter { + name: "val".to_string(), + ty: Type::str_ref(), + has_default: false, + }, + Parameter { + name: "start".to_string(), + ty: Type::int_ref(), + has_default: true, + }, + Parameter { + name: "end".to_string(), + ty: Type::int_ref(), + has_default: true, + }, + ], r#"Return True if string starts with the prefix, otherwise return False. prefix can also be a tuple of prefixes to look for. With optional start, test string beginning at that position. With optional end, stop comparing string at that position."#, false, None, @@ -235,7 +421,13 @@ register_string_member! { strip => Type::function( Some(Rc::new(Type::STR)), Rc::new(Type::STR), - &[], + &[ + Parameter { + name: "chars".to_string(), + ty: Type::str_ref(), + has_default: false, + }, + ], 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:"#, false, None, diff --git a/kclvm/sema/src/resolver/arg.rs b/kclvm/sema/src/resolver/arg.rs index 410835913..5a6492e63 100644 --- a/kclvm/sema/src/resolver/arg.rs +++ b/kclvm/sema/src/resolver/arg.rs @@ -1,5 +1,5 @@ use crate::resolver::Resolver; -use crate::ty::{Parameter, Type}; +use crate::ty::{FunctionType, Type}; use indexmap::IndexSet; use kclvm_ast::ast; use std::rc::Rc; @@ -29,7 +29,7 @@ impl<'ctx> Resolver<'ctx> { func: &ast::Expr, args: &'ctx [ast::NodeRef], kwargs: &'ctx [ast::NodeRef], - params: &[Parameter], + func_ty: &FunctionType, ) { let func_name = self.get_func_name(func); let arg_types = self.exprs(args); @@ -52,53 +52,56 @@ impl<'ctx> Resolver<'ctx> { .add_compile_error("missing argument", kw.get_span_pos()); } } - if !params.is_empty() { - for (i, ty) in arg_types.iter().enumerate() { - let expected_ty = match params.get(i) { - Some(param) => param.ty.clone(), - None => { + for (i, ty) in arg_types.iter().enumerate() { + let expected_ty = match func_ty.params.get(i) { + Some(param) => param.ty.clone(), + None => { + if !func_ty.is_variadic { self.handler.add_compile_error( &format!( "{} takes {} positional argument but {} were given", func_name, - params.len(), + func_ty.params.len(), args.len(), ), args[i].get_span_pos(), ); - return; } - }; - self.must_assignable_to(ty.clone(), expected_ty, args[i].get_span_pos(), None) - } - for (i, (arg_name, kwarg_ty)) in kwarg_types.iter().enumerate() { - if !params - .iter() - .map(|p| p.name.clone()) - .any(|x| x == *arg_name) - { - self.handler.add_compile_error( - &format!( - "{} got an unexpected keyword argument '{}'", - func_name, arg_name - ), - kwargs[i].get_span_pos(), - ); + return; } - let expected_types: Vec> = params - .iter() - .filter(|p| p.name == *arg_name) - .map(|p| p.ty.clone()) - .collect(); - if !expected_types.is_empty() { - self.must_assignable_to( - kwarg_ty.clone(), - expected_types[0].clone(), - kwargs[i].get_span_pos(), - None, - ); - }; + }; + self.must_assignable_to(ty.clone(), expected_ty, args[i].get_span_pos(), None) + } + for (i, (arg_name, kwarg_ty)) in kwarg_types.iter().enumerate() { + if !func_ty + .params + .iter() + .map(|p| p.name.clone()) + .any(|x| x == *arg_name) + && !func_ty.is_variadic + { + self.handler.add_compile_error( + &format!( + "{} got an unexpected keyword argument '{}'", + func_name, arg_name + ), + kwargs[i].get_span_pos(), + ); } + let expected_types: Vec> = func_ty + .params + .iter() + .filter(|p| p.name == *arg_name) + .map(|p| p.ty.clone()) + .collect(); + if !expected_types.is_empty() { + self.must_assignable_to( + kwarg_ty.clone(), + expected_types[0].clone(), + kwargs[i].get_span_pos(), + None, + ); + }; } } } diff --git a/kclvm/sema/src/resolver/node.rs b/kclvm/sema/src/resolver/node.rs index 6e31e6eb8..1f989138d 100644 --- a/kclvm/sema/src/resolver/node.rs +++ b/kclvm/sema/src/resolver/node.rs @@ -7,7 +7,8 @@ use std::rc::Rc; use crate::info::is_private_field; use crate::ty::{ - sup, DictType, Parameter, Type, TypeInferMethods, TypeKind, RESERVED_TYPE_IDENTIFIERS, + sup, DictType, FunctionType, Parameter, Type, TypeInferMethods, TypeKind, + RESERVED_TYPE_IDENTIFIERS, }; use super::format::VALID_FORMAT_SPEC_SET; @@ -516,7 +517,7 @@ impl<'ctx> MutSelfTypedResultWalker<'ctx> for Resolver<'ctx> { &call_expr.func.node, &call_expr.args, &call_expr.keywords, - &[], + &FunctionType::variadic_func(), ); self.any_ty() } else if let TypeKind::Function(func_ty) = &call_ty.kind { @@ -524,7 +525,7 @@ impl<'ctx> MutSelfTypedResultWalker<'ctx> for Resolver<'ctx> { &call_expr.func.node, &call_expr.args, &call_expr.keywords, - &func_ty.params, + &func_ty, ); func_ty.return_ty.clone() } else if let TypeKind::Schema(schema_ty) = &call_ty.kind { @@ -539,7 +540,7 @@ impl<'ctx> MutSelfTypedResultWalker<'ctx> for Resolver<'ctx> { &call_expr.func.node, &call_expr.args, &call_expr.keywords, - &schema_ty.func.params, + &schema_ty.func, ); let mut return_ty = schema_ty.clone(); return_ty.is_instance = true; @@ -876,7 +877,7 @@ impl<'ctx> MutSelfTypedResultWalker<'ctx> for Resolver<'ctx> { &func, &schema_expr.args, &schema_expr.kwargs, - &schema_ty.func.params, + &schema_ty.func, ); } self.any_ty() diff --git a/kclvm/sema/src/resolver/schema.rs b/kclvm/sema/src/resolver/schema.rs index 264a051ae..61accce1b 100644 --- a/kclvm/sema/src/resolver/schema.rs +++ b/kclvm/sema/src/resolver/schema.rs @@ -183,7 +183,7 @@ impl<'ctx> Resolver<'ctx> { &decorator.node.func.node, &decorator.node.args, &decorator.node.keywords, - &func_ty.params, + &func_ty, ); let (arguments, keywords) = self.arguments_to_string( &decorator.node.args, diff --git a/kclvm/sema/src/ty/constructor.rs b/kclvm/sema/src/ty/constructor.rs index 24581b1d6..40cf65ca7 100644 --- a/kclvm/sema/src/ty/constructor.rs +++ b/kclvm/sema/src/ty/constructor.rs @@ -1,6 +1,31 @@ use super::*; impl Type { + /// Construct an int type reference. + #[inline] + pub fn int_ref() -> TypeRef { + Rc::new(Type::INT) + } + /// Construct a float type reference. + #[inline] + pub fn float_ref() -> TypeRef { + Rc::new(Type::FLOAT) + } + /// Construct a bool type reference. + #[inline] + pub fn bool_ref() -> TypeRef { + Rc::new(Type::BOOL) + } + /// Construct a str type reference. + #[inline] + pub fn str_ref() -> TypeRef { + Rc::new(Type::STR) + } + /// Construct a any type reference. + #[inline] + pub fn any_ref() -> TypeRef { + Rc::new(Type::ANY) + } /// Construct a union type #[inline] pub fn union(types: &[Rc]) -> Type { @@ -10,7 +35,7 @@ impl Type { is_type_alias: false, } } - /// Construct a union type ref + /// Construct an union type reference. #[inline] pub fn union_ref(types: &[Rc]) -> Rc { Rc::new(Self::union(types)) diff --git a/kclvm/sema/src/ty/mod.rs b/kclvm/sema/src/ty/mod.rs index a50977955..b193e387a 100644 --- a/kclvm/sema/src/ty/mod.rs +++ b/kclvm/sema/src/ty/mod.rs @@ -377,12 +377,26 @@ impl NumberMultiplierType { pub struct FunctionType { pub doc: String, pub params: Vec, - pub self_ty: Option>, - pub return_ty: Rc, + pub self_ty: Option, + pub return_ty: TypeRef, pub is_variadic: bool, pub kw_only_index: Option, } +impl FunctionType { + #[inline] + pub fn variadic_func() -> Self { + Self { + doc: "".to_string(), + params: vec![], + self_ty: None, + return_ty: Type::any_ref(), + is_variadic: true, + kw_only_index: None, + } + } +} + /// The function parameter. #[derive(Debug, Clone, PartialEq)] pub struct Parameter { diff --git a/kclvm/tools/src/LSP/src/completion.rs b/kclvm/tools/src/LSP/src/completion.rs index ab214e74a..231514c84 100644 --- a/kclvm/tools/src/LSP/src/completion.rs +++ b/kclvm/tools/src/LSP/src/completion.rs @@ -233,7 +233,7 @@ pub(crate) fn get_completion( }); } } - Expr::Config(config_expr) => match parent { + Expr::Config(_) => match parent { Some(schema_expr) => { if let Expr::Schema(schema_expr) = schema_expr.node { let schema_def = diff --git a/test/grammar/types/args/schema_types_err_too_many_args_0/main.k b/test/grammar/types/args/schema_types_err_too_many_args_0/main.k new file mode 100644 index 000000000..b805bcf68 --- /dev/null +++ b/test/grammar/types/args/schema_types_err_too_many_args_0/main.k @@ -0,0 +1,4 @@ +schema SchemaInMainK: + msg?: str + +schema_in_main_k = SchemaInMainK(msg='I am the instance of SchemaInMainK') diff --git a/test/grammar/types/args/schema_types_err_too_many_args_0/stderr.golden.py b/test/grammar/types/args/schema_types_err_too_many_args_0/stderr.golden.py new file mode 100644 index 000000000..7dc21d230 --- /dev/null +++ b/test/grammar/types/args/schema_types_err_too_many_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=4, + col_no=34, + ) + ], + arg_msg='"SchemaInMainK" got an unexpected keyword argument \'msg\'' + ), + file=sys.stdout +) diff --git a/test/grammar/types/args/schema_types_err_too_many_args_1/main.k b/test/grammar/types/args/schema_types_err_too_many_args_1/main.k new file mode 100644 index 000000000..6cdd9cb7a --- /dev/null +++ b/test/grammar/types/args/schema_types_err_too_many_args_1/main.k @@ -0,0 +1,4 @@ +schema SchemaInMainK: + msg?: str + +schema_in_main_k = SchemaInMainK('I am the instance of SchemaInMainK') diff --git a/test/grammar/types/args/schema_types_err_too_many_args_1/stderr.golden.py b/test/grammar/types/args/schema_types_err_too_many_args_1/stderr.golden.py new file mode 100644 index 000000000..51132d69c --- /dev/null +++ b/test/grammar/types/args/schema_types_err_too_many_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=4, + col_no=34, + ) + ], + arg_msg='"SchemaInMainK" takes 0 positional argument but 1 were given' + ), + file=sys.stdout +) diff --git a/test/grammar/types/args/schema_types_err_too_many_args_2/main.k b/test/grammar/types/args/schema_types_err_too_many_args_2/main.k new file mode 100644 index 000000000..de45e7a45 --- /dev/null +++ b/test/grammar/types/args/schema_types_err_too_many_args_2/main.k @@ -0,0 +1,4 @@ +schema SchemaInMainK[m: str]: + msg?: str + +schema_in_main_k = SchemaInMainK('param1', "param2") diff --git a/test/grammar/types/args/schema_types_err_too_many_args_2/stderr.golden.py b/test/grammar/types/args/schema_types_err_too_many_args_2/stderr.golden.py new file mode 100644 index 000000000..a479cb2ca --- /dev/null +++ b/test/grammar/types/args/schema_types_err_too_many_args_2/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=4, + col_no=34, + ) + ], + arg_msg='"SchemaInMainK" takes 1 positional argument but 2 were given' + ), + file=sys.stdout +)