typecheck: diagnose empty CALL arguments
This commit is contained in:
@@ -151,7 +151,7 @@ Function square only accepts 1 argument, but this call gives 2. Remove the extra
|
||||
Function squre is not known yet. Check the function name or add TEACH squre before calling it.
|
||||
```
|
||||
|
||||
The unknown-function diagnostic is validated for both modern `DO squre 4` and compatibility `CALL squre WITH 4` calls.
|
||||
The unknown-function diagnostic is validated for both modern `DO squre 4` and compatibility `CALL squre WITH 4` calls. Compatibility calls that leave `WITH` empty, such as `CALL greet WITH`, now count as zero arguments, so learners get the same missing-argument guidance as `DO greet` instead of the checker treating the blank as an argument.
|
||||
|
||||
For functions with more than one checked parameter, Claro reports each mismatched argument with the parameter name:
|
||||
|
||||
|
||||
@@ -345,6 +345,18 @@ Simple functions also get an arity check before running. If a learner defines `T
|
||||
Function greet needs 1 argument, but this call gives 0. Add the missing argument.
|
||||
```
|
||||
|
||||
The older compatibility spelling gets the same beginner-facing result when `WITH` is left empty:
|
||||
|
||||
```claro
|
||||
CALL greet WITH
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```text
|
||||
Function greet needs 1 argument, but this call gives 0. Add the missing argument.
|
||||
```
|
||||
|
||||
## Status
|
||||
|
||||
This is currently a static checker feature. It improves `claro typecheck` and validation confidence for `DO` and compatibility `CALL ... WITH` function calls, simple `DO object.method ...` calls where the object was created with `NEW Class name`, and direct assignments to known object fields. Runtime enforcement for every container mutation and richer function/object signatures can be added later after the syntax is classroom-tested.
|
||||
|
||||
@@ -52,7 +52,7 @@ Ready now:
|
||||
- typed variables such as `SET score NUMBER 10`
|
||||
- `TYPE OF` and `CHECK TYPE`
|
||||
- typed list/map checks through `claro typecheck`
|
||||
- a narrow function/method argument check: `CHECK TYPE parameter IS TYPE` inside a function or simple object method lets `claro typecheck` accept correct checked calls, catch mismatched `DO`, `CALL ... WITH`, `DO object.method ...`, and compatibility `CALL object.method WITH ...` arguments, including checked methods that appear after another method in the same class, report missing checked function and method arguments, report missing unchecked arguments for simple functions and simple object methods, catch extra arguments to simple functions and checked methods even when the checked method appears after another method in the same class, catch modern `DO` and compatibility `CALL ... WITH` calls to undeclared simple functions, explain when `DO object.method ...` or compatibility `CALL object.method WITH ...` happens before the object is created with `NEW` even if the method name is also wrong, and catch simple calls to undeclared methods with a class-specific `TEACH` hint
|
||||
- a narrow function/method argument check: `CHECK TYPE parameter IS TYPE` inside a function or simple object method lets `claro typecheck` accept correct checked calls, catch mismatched `DO`, `CALL ... WITH`, `DO object.method ...`, and compatibility `CALL object.method WITH ...` arguments, including checked methods that appear after another method in the same class, report missing checked function and method arguments, report missing unchecked arguments for simple functions and simple object methods, treat empty compatibility calls such as `CALL greet WITH` as missing-argument mistakes, catch extra arguments to simple functions and checked methods even when the checked method appears after another method in the same class, catch modern `DO` and compatibility `CALL ... WITH` calls to undeclared simple functions, explain when `DO object.method ...` or compatibility `CALL object.method WITH ...` happens before the object is created with `NEW` even if the method name is also wrong, and catch simple calls to undeclared methods with a class-specific `TEACH` hint
|
||||
- a narrow object-field assignment/check-type check for simple `NEW Class object` plus direct `SET object.field value` and `CHECK TYPE object.field IS TYPE` cases when the class declares `HAS field TYPE`; validation now covers correct NUMBER, TEXT, and YESNO direct assignments, direct `CHECK TYPE` metadata acceptance for NUMBER/TEXT/YESNO fields, negative NUMBER/TEXT/YESNO field `CHECK TYPE` metadata mismatches, NUMBER/TEXT/YESNO-expectation unknown-field `CHECK TYPE` diagnostics, missing-object field assignment and `CHECK TYPE` diagnostics, NUMBER/TEXT/YESNO wrong-type diagnostics, field collection when a `HAS` field appears after a simple method, NUMBER/TEXT/YESNO-valued unknown-field diagnostics for direct assignments to undeclared fields, plus a plain beginner-facing unknown-field diagnostic when the assigned expression type is not inferable yet
|
||||
|
||||
Still needed:
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ See `CURRENT_STATUS.md` for the detailed feature matrix.
|
||||
|
||||
1. Keep beginner-facing docs current and separate from historical release notes.
|
||||
2. Keep examples aligned with the modern simple syntax (`END`, `DO`, short `SET`, short `ASK`) while documenting older compatibility forms separately.
|
||||
3. Expand validation around typecheck diagnostics and package/networking safety. Current Forgejo/Gitea CI runs the documented release gates (`claro validate`, typecheck diagnostics validation, version convention validation, and CI workflow coverage validation). Current package safety validation covers safe manifest/lockfile creation, rejecting unsafe package names during `package add`, detecting unsafe package names already present in `claro.project` during `package doctor`, refusing to write lockfile data for unsafe package names during `package lock`, and making `package remove` fail instead of reporting success when unsafe package names remain during lockfile refresh. Current function validation covers correct checked calls, wrong-type arguments, missing checked-argument diagnostics, missing unchecked-argument diagnostics for simple functions, extra-argument diagnostics, and beginner-facing unknown-function diagnostics for mistyped modern `DO` and compatibility `CALL ... WITH` calls; current object-method parameter validation covers correct modern `DO object.method ...` and compatibility `CALL object.method WITH ...` checked calls, wrong-type diagnostics for both call forms, checked methods after another method in the same class, missing and extra checked-argument diagnostics including the extra-argument case for a checked method after another method in the same class, missing unchecked-argument diagnostics for simple object methods, missing-object guidance when `DO player.method ...` or compatibility `CALL object.method WITH ...` appears before `NEW` even if the method name is also wrong, and a class-specific unknown-method diagnostic when a learner calls a method the class does not declare; object-field validation covers correct NUMBER, TEXT, and YESNO direct `SET object.field value` assignments, direct `CHECK TYPE` metadata acceptance for NUMBER/TEXT/YESNO fields, negative NUMBER/TEXT/YESNO metadata mismatches, NUMBER/TEXT/YESNO-expectation unknown-field `CHECK TYPE` diagnostics, missing-object field assignment and `CHECK TYPE` diagnostics, NUMBER/TEXT/YESNO wrong-type direct assignments, field collection when a `HAS` field appears after a simple method, NUMBER/TEXT/YESNO-valued unknown-field diagnostics, and a beginner-facing unknown-field diagnostic when an undeclared field assignment uses an expression whose type is not inferable yet.
|
||||
3. Expand validation around typecheck diagnostics and package/networking safety. Current Forgejo/Gitea CI runs the documented release gates (`claro validate`, typecheck diagnostics validation, version convention validation, and CI workflow coverage validation). Current package safety validation covers safe manifest/lockfile creation, rejecting unsafe package names during `package add`, detecting unsafe package names already present in `claro.project` during `package doctor`, refusing to write lockfile data for unsafe package names during `package lock`, and making `package remove` fail instead of reporting success when unsafe package names remain during lockfile refresh. Current function validation covers correct checked calls, wrong-type arguments, missing checked-argument diagnostics, missing unchecked-argument diagnostics for simple functions in both modern `DO greet` and empty compatibility `CALL greet WITH` forms, extra-argument diagnostics, and beginner-facing unknown-function diagnostics for mistyped modern `DO` and compatibility `CALL ... WITH` calls; current object-method parameter validation covers correct modern `DO object.method ...` and compatibility `CALL object.method WITH ...` checked calls, wrong-type diagnostics for both call forms, checked methods after another method in the same class, missing and extra checked-argument diagnostics including the extra-argument case for a checked method after another method in the same class, missing unchecked-argument diagnostics for simple object methods, missing-object guidance when `DO player.method ...` or compatibility `CALL object.method WITH ...` appears before `NEW` even if the method name is also wrong, and a class-specific unknown-method diagnostic when a learner calls a method the class does not declare; object-field validation covers correct NUMBER, TEXT, and YESNO direct `SET object.field value` assignments, direct `CHECK TYPE` metadata acceptance for NUMBER/TEXT/YESNO fields, negative NUMBER/TEXT/YESNO metadata mismatches, NUMBER/TEXT/YESNO-expectation unknown-field `CHECK TYPE` diagnostics, missing-object `CHECK TYPE` and direct field assignment diagnostics, NUMBER/TEXT/YESNO wrong-type diagnostics, field collection after simple methods, NUMBER/TEXT/YESNO-valued unknown-field diagnostics, and beginner-facing fallback wording when an unknown-field assignment expression type is not inferable yet.
|
||||
4. Add small examples for each foundation feature before adding bigger syntax.
|
||||
|
||||
## Complete-platform milestones
|
||||
|
||||
+1
-1
@@ -600,7 +600,7 @@ static int typecheck_file(const char *path){ FILE *f=fopen(path,"rb"); char line
|
||||
else if(!strcmp(up,"IMPORT")){ const char *as=find_word_ci(t,"AS"); if(as){ const char *pcur=as+2; char *alias=unquote_token(&pcur); if(alias&&*trim_inplace(alias)) type_env_set(&types,trim_inplace(alias),"MODULE"); free(alias); } }
|
||||
else if(!strcmp(up,"TYPE")&&starts_ci(t,"TYPE OF")){ char *name=check_var_after_as(t); if(name){ type_env_set(&types,name,"TEXT"); free(name); } }
|
||||
else if(!strcmp(up,"NEW")){ const char *as=find_word_ci(t,"AS"); char *name=NULL; char *cls=NULL; char objtype[256]; if(as){ cls=substr(t+3,as); name=xstrdup(trim_inplace((char*)as+2)); } else { const char *pcur=t+3; cls=unquote_token(&pcur); name=unquote_token(&pcur); } snprintf(objtype,sizeof(objtype),"OBJECT:%s",trim_inplace(cls)); if(name&&*trim_inplace(name)){ TypeFieldCheck *fc; char fullname[256]; char *nt=trim_inplace(name); char *ct=trim_inplace(cls); type_env_set(&types,nt,objtype); for(fc=field_checks;fc;fc=fc->next){ if(ci_eq(fc->cls,ct)){ snprintf(fullname,sizeof(fullname),"%s.%s",nt,fc->field); type_env_set(&types,fullname,fc->type); } } } free(cls); free(name); }
|
||||
else if((!strcmp(up,"DO")&&!find_word_ci(t,"TIMES"))||!strcmp(up,"CALL")){ char *fname=NULL; char **parts=NULL; int ac=0,i,is_method=0; char lookup[256],display[256]; if(!strcmp(up,"DO")){ const char *pcur=t+2; fname=unquote_token(&pcur); if(*trim_inplace((char*)pcur)) ac=split_args(pcur,&parts); } else { char *rest=t+4; const char *with=find_word_ci(rest,"WITH"); fname=with?substr(rest,with):xstrdup(rest); if(with) ac=split_args(with+4,&parts); } snprintf(lookup,sizeof(lookup),"%s",trim_inplace(fname)); snprintf(display,sizeof(display),"%s",lookup); { char *dot=strchr(lookup,'.'); if(dot){ char objname[128]; const char *objtype; snprintf(objname,sizeof(objname),"%.*s",(int)(dot-lookup),lookup); objtype=type_env_get(types,objname); if(objtype&&starts_ci(objtype,"OBJECT:")){ char method[128]; const char *cls=objtype+7; snprintf(method,sizeof(method),"%s",dot+1); if(!type_method_check_for(method_checks,cls,method)){ printf("%s:%d: Object %s has no method %s. Check the method name or add TEACH %s inside CLASS %s.\n",path,line_no,cls,method,method,cls); errs++; } snprintf(lookup,sizeof(lookup),"%s.%s",cls,method); snprintf(display,sizeof(display),"%s",lookup); is_method=1; }
|
||||
else if((!strcmp(up,"DO")&&!find_word_ci(t,"TIMES"))||!strcmp(up,"CALL")){ char *fname=NULL; char **parts=NULL; int ac=0,i,is_method=0; char lookup[256],display[256]; if(!strcmp(up,"DO")){ const char *pcur=t+2; fname=unquote_token(&pcur); if(*trim_inplace((char*)pcur)) ac=split_args(pcur,&parts); } else { char *rest=t+4; const char *with=find_word_ci(rest,"WITH"); fname=with?substr(rest,with):xstrdup(rest); if(with&&*trim_inplace((char*)with+4)) ac=split_args(with+4,&parts); } snprintf(lookup,sizeof(lookup),"%s",trim_inplace(fname)); snprintf(display,sizeof(display),"%s",lookup); { char *dot=strchr(lookup,'.'); if(dot){ char objname[128]; const char *objtype; snprintf(objname,sizeof(objname),"%.*s",(int)(dot-lookup),lookup); objtype=type_env_get(types,objname); if(objtype&&starts_ci(objtype,"OBJECT:")){ char method[128]; const char *cls=objtype+7; snprintf(method,sizeof(method),"%s",dot+1); if(!type_method_check_for(method_checks,cls,method)){ printf("%s:%d: Object %s has no method %s. Check the method name or add TEACH %s inside CLASS %s.\n",path,line_no,cls,method,method,cls); errs++; } snprintf(lookup,sizeof(lookup),"%s.%s",cls,method); snprintf(display,sizeof(display),"%s",lookup); is_method=1; }
|
||||
else if(!objtype||(!starts_ci(objtype,"MODULE")&&(!strcmp(up,"DO")||type_method_name_exists(method_checks,dot+1)))){ printf("%s:%d: Object %s is not known yet. Create it with NEW ClassName %s before calling %s.\n",path,line_no,objname,objname,lookup); errs++; } } } for(i=0;i<ac;i++){ const char *param=NULL; const char *need=type_param_check_for(param_checks,lookup,i,¶m); const char *got=simple_expr_type(types,parts[i]); if(need&&got&&!type_words_match(need,got)){ printf("%s:%d: Type mismatch for %s %s: parameter %s needs %s, but this argument looks like %s.\n",path,line_no,is_method?"method":"function",display,param?param:"argument",need,got); errs++; } } { TypeParamCheck *pc=param_checks; while(pc){ if(ci_eq(pc->func,lookup)&&pc->pos>=ac){ printf("%s:%d: %s %s needs argument %s as %s, but this call does not provide it.\n",path,line_no,is_method?"Method":"Function",display,pc->param,pc->type); errs++; } pc=pc->next; } } { int expected=type_arity_check_for(arity_checks,lookup); if(expected>=0&&ac>expected){ printf("%s:%d: %s %s only accepts %d %s, but this call gives %d. Remove the extra %s.\n",path,line_no,is_method?"Method":"Function",display,expected,expected==1?"argument":"arguments",ac,ac-expected==1?"argument":"arguments"); errs++; } else if(expected>=0&&ac<expected){ int has_checked_missing=0; TypeParamCheck *mpc=param_checks; while(mpc){ if(ci_eq(mpc->func,lookup)&&mpc->pos>=ac){ has_checked_missing=1; break; } mpc=mpc->next; } if(!has_checked_missing){ printf("%s:%d: %s %s needs %d %s, but this call gives %d. Add the missing %s.\n",path,line_no,is_method?"Method":"Function",display,expected,expected==1?"argument":"arguments",ac,expected-ac==1?"argument":"arguments"); errs++; } } else if((!strcmp(up,"DO")||!strcmp(up,"CALL"))&&!is_method&&!strchr(lookup,'.')&&expected<0){ printf("%s:%d: Function %s is not known yet. Check the function name or add TEACH %s before calling it.\n",path,line_no,lookup,lookup); errs++; } } free(fname); }
|
||||
else if(!strcmp(up,"CHECK")&&starts_ci(t,"CHECK TYPE")){ const char *is=find_word_ci(t,"IS"); if(is){ char *expr=substr(t+10,is); char *need=xstrdup(trim_inplace((char*)is+2)); char *et=trim_inplace(expr); const char *got=simple_expr_type(types,et); if(got&&!type_words_match(need,got)){ printf("%s:%d: Type check failed: expected %s, but %s looks like %s.\n",path,line_no,need,et,got); errs++; } else if(!got&&strchr(et,'.')){ char objname[128],fieldname[128]; char *dot=strchr(et,'.'); const char *objtype; snprintf(objname,sizeof(objname),"%.*s",(int)(dot-et),et); snprintf(fieldname,sizeof(fieldname),"%s",dot+1); objtype=type_env_get(types,objname); if(objtype&&starts_ci(objtype,"OBJECT:")&&!type_field_check_for(field_checks,objtype+7,fieldname)){ printf("%s:%d: Object %s has no field %s. Check the field name or add HAS %s %s to the class.\n",path,line_no,objtype+7,fieldname,fieldname,need); errs++; } else if(!objtype){ printf("%s:%d: Object %s is not known yet. Create it with NEW ClassName %s before checking %s.\n",path,line_no,objname,objname,et); errs++; } } free(expr); free(need); } else { printf("%s:%d: CHECK TYPE needs IS. Try: CHECK TYPE score IS NUMBER\n",path,line_no); errs++; } }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
TEACH greet name
|
||||
SAY name
|
||||
END
|
||||
|
||||
CALL greet WITH
|
||||
@@ -29,6 +29,9 @@ EXPECTED = {
|
||||
"tests/typecheck_function_missing_unchecked_arg_bad.claro": [
|
||||
"tests/typecheck_function_missing_unchecked_arg_bad.claro:5: Function greet needs 1 argument, but this call gives 0. Add the missing argument.",
|
||||
],
|
||||
"tests/typecheck_function_call_missing_unchecked_arg_bad.claro": [
|
||||
"tests/typecheck_function_call_missing_unchecked_arg_bad.claro:5: Function greet needs 1 argument, but this call gives 0. Add the missing argument.",
|
||||
],
|
||||
"tests/typecheck_function_extra_arg_bad.claro": [
|
||||
"tests/typecheck_function_extra_arg_bad.claro:6: Function square only accepts 1 argument, but this call gives 2. Remove the extra argument.",
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user