From 8153aed9cbc018c0c25e723991d542b1fd1c7919 Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Tue, 1 Sep 2026 05:07:58 +0000 Subject: [PATCH] typecheck: diagnose CALL method before NEW --- README.md | 2 +- docs/ADVANCED_STATIC_TYPING.md | 3 ++- docs/CURRENT_STATUS.md | 2 +- docs/ROADMAP.md | 2 +- src/claro.c | 3 ++- tests/typecheck_method_call_unknown_object_bad.claro | 10 ++++++++++ tools/validate_typecheck_diagnostics.py | 3 +++ 7 files changed, 20 insertions(+), 5 deletions(-) create mode 100644 tests/typecheck_method_call_unknown_object_bad.claro diff --git a/README.md b/README.md index e8cffc2..60e6586 100644 --- a/README.md +++ b/README.md @@ -178,7 +178,7 @@ DO player.add "five" Type mismatch for method Player.add: parameter points needs NUMBER, but this argument looks like TEXT. ``` -If a learner calls a simple object method before creating the object with `NEW`, `claro typecheck` now points to the missing setup instead of letting the dotted call look like an ordinary unknown function: +If a learner calls a simple object method before creating the object with `NEW`, `claro typecheck` now points to the missing setup instead of letting the dotted call look like an ordinary unknown function. This covers both the modern `DO player.add 5` form and the older compatibility `CALL player.add WITH 5` form: ```text Object player is not known yet. Create it with NEW ClassName player before calling player.add. diff --git a/docs/ADVANCED_STATIC_TYPING.md b/docs/ADVANCED_STATIC_TYPING.md index d7e183e..5b989e8 100644 --- a/docs/ADVANCED_STATIC_TYPING.md +++ b/docs/ADVANCED_STATIC_TYPING.md @@ -125,7 +125,7 @@ Type mismatch for method Player.add: parameter points needs NUMBER, but this arg Both sides of this narrow method foundation are covered by validation: `tests/typecheck_method_good.claro` checks that `DO player.add 5` is accepted, and `tests/typecheck_method_bad.claro` checks the friendly wrong-type diagnostic. -If the method call comes before the object is created, the type checker now gives the learner the missing setup step: +If the method call comes before the object is created, the type checker now gives the learner the missing setup step. The modern `DO` form and the older compatibility `CALL ... WITH` form both get this guidance: ```claro CLASS Player @@ -138,6 +138,7 @@ CLASS Player END DO player.add 5 +CALL player.add WITH 5 ``` Output: diff --git a/docs/CURRENT_STATUS.md b/docs/CURRENT_STATUS.md index 0447f40..dfacb19 100644 --- a/docs/CURRENT_STATUS.md +++ b/docs/CURRENT_STATUS.md @@ -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`, and `DO object.method ...` arguments, explain when a `DO object.method ...` call happens before the object is created with `NEW`, 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`, and `DO object.method ...` arguments, explain when `DO object.method ...` or compatibility `CALL object.method WITH ...` happens before the object is created with `NEW`, 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, 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: diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index d77e874..5e6e72c 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -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 object-method parameter validation covers one correct `DO object.method ...` call, one wrong-type diagnostic, missing-object guidance when `DO player.method ...` appears before `NEW`, 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, simple NUMBER/TEXT/YESNO-valued unknown-field diagnostics after `NEW Class object`, and a beginner-facing fallback when the unknown field's assigned expression 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 object-method parameter validation covers one correct `DO object.method ...` call, one wrong-type diagnostic, missing-object guidance when `DO player.method ...` or compatibility `CALL player.method WITH ...` appears before `NEW`, 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, simple NUMBER/TEXT/YESNO-valued unknown-field diagnostics after `NEW Class object`, and a beginner-facing fallback when the unknown field's assigned expression type is not inferable yet. 4. Add small examples for each foundation feature before adding bigger syntax. ## Complete-platform milestones diff --git a/src/claro.c b/src/claro.c index e878666..5ec59dd 100644 --- a/src/claro.c +++ b/src/claro.c @@ -542,6 +542,7 @@ static void add_type_field_check(TypeFieldCheck **checks,const char *cls,const c static const char *type_field_check_for(TypeFieldCheck *checks,const char *cls,const char *field){ TypeFieldCheck *c=checks; while(c){ if(ci_eq(c->cls,cls)&&ci_eq(c->field,field)) return c->type; c=c->next; } return NULL; } static void add_type_method_check(TypeMethodCheck **checks,const char *cls,const char *method){ TypeMethodCheck *c; if(!cls||!*cls||!method||!*method) return; c=(TypeMethodCheck*)xmalloc(sizeof(TypeMethodCheck)); c->cls=xstrdup(cls); c->method=xstrdup(method); c->next=*checks; *checks=c; } static int type_method_check_for(TypeMethodCheck *checks,const char *cls,const char *method){ TypeMethodCheck *c=checks; while(c){ if(ci_eq(c->cls,cls)&&ci_eq(c->method,method)) return 1; c=c->next; } return 0; } +static int type_method_name_exists(TypeMethodCheck *checks,const char *method){ TypeMethodCheck *c=checks; while(c){ if(ci_eq(c->method,method)) return 1; c=c->next; } return 0; } static int param_index_named(char **params,int pcnt,const char *name){ int i; char tmp[256]; char *p; for(i=0;i=0) add_type_param_check(checks,func_name,en,need,idx); free(expr); free(need); } } } } static TypeParamCheck *collect_function_param_type_checks(FILE *f){ TypeParamCheck *checks=NULL; char line[4096]; char current_class[128]=""; while(fgets(line,sizeof(line),f)){ char tmp[4096],up[64],w[128]; char *t; strncpy(tmp,line,sizeof(tmp)-1); tmp[sizeof(tmp)-1]=0; t=trim_inplace(tmp); if(is_blank_or_comment(t)) continue; first_word(t,w,sizeof(w)); upper_copy(up,w,sizeof(up)); if(!strcmp(up,"CLASS")){ const char *pcur=t+5; char *cls=unquote_token(&pcur); snprintf(current_class,sizeof(current_class),"%s",trim_inplace(cls)); free(cls); continue; } if((!strcmp(up,"END")||!strcmp(up,"ENDCLASS"))&¤t_class[0]){ current_class[0]=0; continue; } if(!strcmp(up,"TEACH")){ char teachline[4096]; char *fname=NULL; char **params=NULL; int pcnt=0; char full_name[256]; strncpy(teachline,t,sizeof(teachline)-1); teachline[sizeof(teachline)-1]=0; parse_teach_parts(teachline,&fname,¶ms,&pcnt); if(current_class[0]) snprintf(full_name,sizeof(full_name),"%s.%s",current_class,fname); else snprintf(full_name,sizeof(full_name),"%s",fname); collect_teach_param_checks_from_body(&checks,f,full_name,params,pcnt); } } rewind(f); return checks; } @@ -554,7 +555,7 @@ static int typecheck_file(const char *path){ FILE *f=fopen(path,"rb"); char line 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")){ 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