diff --git a/CHANGELOG.md b/CHANGELOG.md index 649ca72..2fd4706 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +### Diagnose missing function names + +- `claro typecheck` now explains how to repair a bare `TEACH` declaration instead of silently accepting a function with no name. +- Added focused coverage to the complete typecheck diagnostic validation matrix. + ### Diagnose unknown class field types - `claro typecheck` now rejects class fields such as `HAS score BANANA` with a beginner-facing diagnostic that names the class and field, lists supported type examples, and suggests using a Claro type. diff --git a/README.md b/README.md index deb4b56..daa3588 100644 --- a/README.md +++ b/README.md @@ -183,7 +183,7 @@ If a function repeats a parameter name, `claro typecheck` reports the declaratio Function greet declares parameter name more than once. Give each parameter a different name. ``` -Top-level functions must also have unique names. If a script declares `TEACH greet` twice, `claro typecheck` reports `Function greet is declared more than once. Give each function a different name.` Rename one function before calling it. +Top-level functions must also have unique names. If a script declares `TEACH greet` twice, `claro typecheck` reports `Function greet is declared more than once. Give each function a different name.` Rename one function before calling it. A function declaration must include a name; writing only `TEACH` reports `TEACH needs a function name. Add a name after TEACH, such as TEACH greet.` The same declaration check applies to object methods in both syntax styles. For example, `TEACH greet name, name` inside `CLASS Player` reports `Method Player.greet declares parameter name more than once. Give each parameter a different name.` Duplicate method names are also rejected in both modern `END` methods and compatibility `TAKES` / `LEARNED` methods, with a rename hint before a call can become ambiguous. diff --git a/docs/CURRENT_STATUS.md b/docs/CURRENT_STATUS.md index 042d8a4..148c300 100644 --- a/docs/CURRENT_STATUS.md +++ b/docs/CURRENT_STATUS.md @@ -56,7 +56,7 @@ Ready now: - simple function and object-method return checks with `TEACH name ... RETURNS TYPE`, including learner-facing diagnostics for unknown declared return types, mismatched or not-yet-inferable `RETURN` expressions (including method-specific diagnostics naming the class and method, with modern and compatibility method coverage), empty `RETURN` statements, declarations that contain no `RETURN`, and declarations whose only return is inside an incomplete conditional; `RETURNS` is case-insensitive like other Claro keywords and has positive coverage in both modern and compatibility function forms, including lowercase compatibility declarations and lowercase compatibility method declarations, plus a lowercase compatibility-method mismatch diagnostic; `CHECK TYPE` parameter metadata also informs arithmetic return-expression checking, including method return expressions; positive function-return coverage includes numeric multiplication; compatibility methods now have dedicated positive numeric addition, subtraction, division, and multiplication return fixtures plus compatibility addition, subtraction, division, and multiplication diagnostic fixtures; NUMBER return diagnostics name a known TEXT operand and arithmetic operation instead of hiding the cause behind a generic mismatch, with focused addition, subtraction, multiplication, and division coverage for functions and methods; typed methods can also return class-declared fields by simple name, with positive and mismatch coverage in both modern and compatibility method syntax - 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 in both modern `DO` and compatibility `CALL ... WITH` forms, treat empty compatibility calls such as `CALL greet WITH` and `CALL player.rename 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 ...` happens before the object is created with `NEW` even if the method name is also wrong, and catch modern plus compatibility calls to undeclared object methods with a class-specific `TEACH` hint - function and method declarations now reject repeated parameter names with a direct repair hint before calls are checked, including modern and compatibility `TAKES` / `LEARNED` functions and methods; object methods with the same name are also rejected within a class with a rename hint in both modern and compatibility method syntax -- top-level function declarations now reject repeated names with a direct repair hint, so two `TEACH greet` blocks cannot silently compete for one callable function +- top-level function declarations now reject repeated names with a direct repair hint, so two `TEACH greet` blocks cannot silently compete for one callable function; a missing function name after `TEACH` gets a direct beginner-facing repair hint - class declarations now reject repeated field names within the same class with a direct repair hint before field types are used, so two `HAS score ...` lines cannot silently choose conflicting metadata; different classes may independently use the same field name - class field declarations now reject missing types such as `HAS score`, unknown types such as `HAS score BANANA`, and extra tokens such as `HAS score NUMBER TEXT` with the field name, class name, supported type examples, and a repair hint before object-field checks use that metadata - class declarations now reject repeated class names with a direct repair hint, so two `CLASS Player` blocks cannot silently compete for the same name; extra words after a class name also get a direct repair hint instead of becoming part of the class identity diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 8c84615..97fde80 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -54,6 +54,7 @@ Keep declared return types honest: `claro typecheck` now reports friendly diagno 8g. Keep unknown inline method-field annotation diagnostics aligned across syntax generations: compatibility `TAKES` / `LEARNED` methods now have matching focused coverage for misspelled annotations. 8h. Keep function and method declarations unambiguous: `claro typecheck` now reports repeated parameter names with a repair hint, before duplicate names can make argument diagnostics confusing, including modern and compatibility `TAKES` / `LEARNED` functions and methods. Duplicate object method names within one class now get the same declaration-time protection and a rename hint, with focused coverage for both modern and compatibility method syntax. Broader signature validation remains planned. 8h.1. Keep top-level function declarations unambiguous: duplicate `TEACH greet` blocks now get a declaration-time diagnostic with a rename hint, preventing two functions from competing for one name. Broader signature validation remains planned. +8h.2. Keep function declarations understandable: a bare `TEACH` now gets a direct diagnostic explaining that a function name is required, with a small example repair. Broader signature validation remains planned. 8i. Keep class declarations unambiguous: duplicate `HAS` field names within one class now get a declaration-time diagnostic with a repair hint, preventing conflicting field types from being silently accepted, while same-named fields in different classes remain valid. Broader object declaration validation remains planned. 8j. Keep class names unambiguous: duplicate `CLASS Player` declarations now get a declaration-time diagnostic with a rename hint, preventing two class definitions from competing for one name. Extra words after a class name also get a declaration-time repair hint, so a typo such as `CLASS Player Extra` cannot silently create a surprising class identity. diff --git a/src/claro.c b/src/claro.c index a02720e..540f081 100644 --- a/src/claro.c +++ b/src/claro.c @@ -627,7 +627,7 @@ static TypeFieldCheck *collect_class_field_type_checks(FILE *f){ static TypeMethodCheck *collect_class_method_type_checks(FILE *f){ TypeMethodCheck *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(current_class[0]&&!strcmp(up,"TEACH")){ char teachline[4096]; char *fname=NULL; char **params=NULL; int pcnt=0; strncpy(teachline,t,sizeof(teachline)-1); teachline[sizeof(teachline)-1]=0; parse_teach_parts(teachline,&fname,¶ms,&pcnt); add_type_method_check(&checks,current_class,trim_inplace(fname)); while(fgets(line,sizeof(line),f)){ char inner[4096],iup[64],iw[128]; char *it; strncpy(inner,line,sizeof(inner)-1); inner[sizeof(inner)-1]=0; it=trim_inplace(inner); if(is_blank_or_comment(it)) continue; first_word(it,iw,sizeof(iw)); upper_copy(iup,iw,sizeof(iup)); if(!strcmp(iup,"END")||!strcmp(iup,"LEARNED")) break; } } } rewind(f); return checks; } static int typecheck_file(const char *path){ FILE *f=fopen(path,"rb"); char line[4096]; int line_no=0,errs=0; Var *types=NULL; TypeParamCheck *param_checks=NULL; TypeFieldCheck *field_checks=NULL; TypeMethodCheck *method_checks=NULL; TypeArityCheck *arity_checks=NULL; TypeReturnCheck *return_checks=NULL; char current_func[256]=""; char current_method[256]=""; char current_class[128]=""; char declared_classes[128][256]; char declared_functions[128][256]; char declared_methods[128][256]; char declared_fields[128][256]; char declared_objects[128][256]; int declared_class_count=0,declared_function_count=0,declared_method_count=0,declared_field_count=0,declared_object_count=0; int in_teach=0,return_seen=0,top_level_return_seen=0,nested_blocks=0,conditional_paths_complete=1; int branch_returned[64],branch_has_else[64]; if(!f){ fprintf(stderr,"Could not open %s\n",path); return 1; } param_checks=collect_function_param_type_checks(f); arity_checks=collect_function_arity_checks(f); return_checks=collect_function_return_type_checks(f); field_checks=collect_class_field_type_checks(f); method_checks=collect_class_method_type_checks(f); type_env_set(&types,"RESULT","ANY"); type_env_set(&types,"LASTERROR","TEXT"); type_env_set(&types,"LASTERRORFILE","TEXT"); type_env_set(&types,"LASTERRORLINE","NUMBER"); type_env_set(&types,"LASTEXIT","NUMBER"); while(fgets(line,sizeof(line),f)){ char tmp[4096],up[64],w[128]; char *t; line_no++; 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); char *name=trim_inplace(cls); char *extra=trim_inplace((char *)pcur); int duplicate=0,ci; for(ci=0;cinext) if(ci_eq(fc->cls,current_class)) type_env_set(&types,fc->field,fc->type); } { const char *declared_return=type_return_check_for(return_checks,full_name); if(declared_return&&!claro_is_type_word(declared_return)){ printf("%s:%d: %s %s declares an unknown return type %s. Use a Claro type such as NUMBER, TEXT, YESNO, LIST, or MAP.\n",path,line_no,strchr(full_name,'.')?"Method":"Function",full_name,declared_return); errs++; } else if(declared_return) snprintf(current_func,sizeof(current_func),"%s",full_name); } } +else if(!strcmp(up,"TEACH")){ char teachline[4096],full_name[256],*fname=NULL,**params=NULL; int pcnt=0; int duplicate_method=0; strncpy(teachline,t,sizeof(teachline)-1); teachline[sizeof(teachline)-1]=0; parse_teach_parts(teachline,&fname,¶ms,&pcnt); if(!fname||!*trim_inplace(fname)){ printf("%s:%d: TEACH needs a function name. Add a name after TEACH, such as TEACH greet.\n",path,line_no); errs++; } if(current_class[0]) snprintf(full_name,sizeof(full_name),"%s.%s",current_class,fname); else snprintf(full_name,sizeof(full_name),"%s",fname); if(current_class[0]){ int mi; for(mi=0;minext) if(ci_eq(fc->cls,current_class)) type_env_set(&types,fc->field,fc->type); } { const char *declared_return=type_return_check_for(return_checks,full_name); if(declared_return&&!claro_is_type_word(declared_return)){ printf("%s:%d: %s %s declares an unknown return type %s. Use a Claro type such as NUMBER, TEXT, YESNO, LIST, or MAP.\n",path,line_no,strchr(full_name,'.')?"Method":"Function",full_name,declared_return); errs++; } else if(declared_return) snprintf(current_func,sizeof(current_func),"%s",full_name); } } else if(!strcmp(up,"IF")){ if(in_teach){ if(nested_blocks<64){ branch_returned[nested_blocks]=0; branch_has_else[nested_blocks]=0; } nested_blocks++; } } else if(!strcmp(up,"ELSE")){ if(in_teach&&nested_blocks>0&&nested_blocks<=64){ if(!branch_returned[nested_blocks-1]) conditional_paths_complete=0; branch_has_else[nested_blocks-1]=1; branch_returned[nested_blocks-1]=0; } } else if(!strcmp(up,"END")||!strcmp(up,"LEARNED")||!strcmp(up,"ENDIF")){ if(in_teach&&nested_blocks>0){ int nested_complete=(nested_blocks<=64&&branch_has_else[nested_blocks-1]&&branch_returned[nested_blocks-1]); if(nested_blocks<=64&&(!branch_has_else[nested_blocks-1]||!branch_returned[nested_blocks-1])) conditional_paths_complete=0; nested_blocks--; if(nested_complete&&nested_blocks>0&&nested_blocks<=64) branch_returned[nested_blocks-1]=1; } else if(in_teach){ if(current_func[0]&&!return_seen){ const char *need=type_return_check_for(return_checks,current_func); printf("%s:%d: %s %s declares RETURNS %s but has no RETURN statement. Add RETURN with a %s value.\n",path,line_no,strchr(current_func,'.')?"Method":"Function",current_func,need,need); errs++; } else if(current_func[0]&&(!top_level_return_seen&&!conditional_paths_complete)){ const char *need=type_return_check_for(return_checks,current_func); printf("%s:%d: %s %s declares RETURNS %s but does not return a %s value on every path. Add RETURN to each branch or after the conditional.\n",path,line_no,strchr(current_func,'.')?"Method":"Function",current_func,need,need); errs++; } in_teach=0; current_func[0]=0; current_method[0]=0; } else current_class[0]=0; } diff --git a/tests/typecheck_missing_function_name_bad.claro b/tests/typecheck_missing_function_name_bad.claro new file mode 100644 index 0000000..7ab3d8d --- /dev/null +++ b/tests/typecheck_missing_function_name_bad.claro @@ -0,0 +1,3 @@ +TEACH + SAY "hello" +END diff --git a/tools/validate_typecheck_diagnostics.py b/tools/validate_typecheck_diagnostics.py index 4db6bc3..c91c352 100644 --- a/tools/validate_typecheck_diagnostics.py +++ b/tools/validate_typecheck_diagnostics.py @@ -34,6 +34,9 @@ EXPECTED = { "tests/typecheck_duplicate_function_bad.claro": [ "tests/typecheck_duplicate_function_bad.claro:5: Function greet is declared more than once. Give each function a different name.", ], + "tests/typecheck_missing_function_name_bad.claro": [ + "tests/typecheck_missing_function_name_bad.claro:1: TEACH needs a function name. Add a name after TEACH, such as TEACH greet.", + ], "tests/typecheck_duplicate_object_bad.claro": [ "tests/typecheck_duplicate_object_bad.claro:6: Object player is created more than once. Give each object a different name.", ],