From b0dbe976de7d4c9cda54be54c66df4e4da39cedf Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Thu, 17 Sep 2026 06:25:04 +0000 Subject: [PATCH] typecheck: diagnose missing class names --- CHANGELOG.md | 5 +++++ README.md | 2 +- docs/CURRENT_STATUS.md | 1 + docs/ROADMAP.md | 2 ++ src/claro.c | 2 +- tests/typecheck_missing_class_name_bad.claro | 3 +++ tools/validate_typecheck_diagnostics.py | 3 +++ 7 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 tests/typecheck_missing_class_name_bad.claro diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fd4706..9c9cfc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +### Diagnose missing class names + +- `claro typecheck` now explains how to repair a bare `CLASS` declaration instead of silently accepting a class with no name. +- Added focused coverage to the complete typecheck diagnostic validation matrix. + ### Diagnose missing function names - `claro typecheck` now explains how to repair a bare `TEACH` declaration instead of silently accepting a function with no name. diff --git a/README.md b/README.md index daa3588..3f893d6 100644 --- a/README.md +++ b/README.md @@ -191,7 +191,7 @@ Object methods must also have unique names within their class. If `CLASS Player` Class fields must also have unique names and known types. If `CLASS Player` declares `HAS score NUMBER` twice, or declares the same field with another type, `claro typecheck` reports `Class Player declares field score more than once. Give each field a different name.` If a field omits its type, it reports `Class Player field score is missing a type. Add a type such as NUMBER, TEXT, YESNO, LIST, or MAP after the field name.` If a field uses an unknown type such as `BANANA`, it reports `Class Player field score uses an unknown type BANANA. Use a Claro type such as NUMBER, TEXT, YESNO, LIST, or MAP.` If extra text follows a valid type, it reports `Class Player field score has extra text after type NUMBER. Keep only the field name and one type.` Keep one `HAS` line for each field and use one supported type. -Class names must also be unique and use one name only. If a file declares `CLASS Player` twice, `claro typecheck` reports `Class Player is declared more than once. Give each class a different name.` If extra words follow the name, it reports `Class Player has extra text after its name. Keep only the class name after CLASS.` Rename or simplify the declaration before creating its objects. Object names must also be unique within a script: creating `NEW Player player` twice reports `Object player is created more than once. Give each object a different name.` Use a different object name for the second instance. +Class declarations must include a name. A bare `CLASS` reports `CLASS needs a class name. Add a name after CLASS, such as CLASS Player.` Class names must also be unique and use one name only. If a file declares `CLASS Player` twice, `claro typecheck` reports `Class Player is declared more than once. Give each class a different name.` If extra words follow the name, it reports `Class Player has extra text after its name. Keep only the class name after CLASS.` Rename or simplify the declaration before creating its objects. Object names must also be unique within a script: creating `NEW Player player` twice reports `Object player is created more than once. Give each object a different name.` Use a different object name for the second instance. Extra words after an object name are also rejected, for example `NEW Player player extra` reports `NEW Player has extra text after object name player. Keep only the class and object names.` Keep each `NEW` declaration to one class name and one object name. Simple functions and object methods can also declare a return type. `claro typecheck` checks each `RETURN` expression against it and reports a missing return when a declaration never returns a value: diff --git a/docs/CURRENT_STATUS.md b/docs/CURRENT_STATUS.md index 148c300..82fe02c 100644 --- a/docs/CURRENT_STATUS.md +++ b/docs/CURRENT_STATUS.md @@ -57,6 +57,7 @@ Ready now: - 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; a missing function name after `TEACH` gets a direct beginner-facing repair hint +- class declarations now reject a missing class name after `CLASS` with 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 97fde80..fd755b0 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -65,6 +65,8 @@ Keep declared return types honest: `claro typecheck` now reports friendly diagno 8m. Keep class field metadata understandable: a missing or unknown type in a `HAS field TYPE` declaration, or extra text after a valid type, now gets a class-and-field diagnostic with supported type examples or a direct repair hint before object-field checks use that metadata. 8n. Keep object creation unambiguous: extra words after `NEW ClassName object` now get a direct repair hint, so a malformed object declaration cannot silently discard part of the learner's input. +8o. Keep class declarations understandable: a bare `CLASS` now gets a direct diagnostic explaining that a class name is required, with a small example repair. + ### 1. Strong static types Goal: make larger beginner programs safer without making first scripts harder. diff --git a/src/claro.c b/src/claro.c index 540f081..0de8cfc 100644 --- a/src/claro.c +++ b/src/claro.c @@ -625,7 +625,7 @@ static TypeFieldCheck *collect_class_field_type_checks(FILE *f){ return checks; } 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,"IF")){ if(in_teach){ if(nested_blocks<64){ branch_returned[nested_blocks]=0; branch_has_else[nested_blocks]=0; } nested_blocks++; } } diff --git a/tests/typecheck_missing_class_name_bad.claro b/tests/typecheck_missing_class_name_bad.claro new file mode 100644 index 0000000..66363b6 --- /dev/null +++ b/tests/typecheck_missing_class_name_bad.claro @@ -0,0 +1,3 @@ +CLASS + HAS score NUMBER +END \ No newline at end of file diff --git a/tools/validate_typecheck_diagnostics.py b/tools/validate_typecheck_diagnostics.py index c91c352..02d15a2 100644 --- a/tools/validate_typecheck_diagnostics.py +++ b/tools/validate_typecheck_diagnostics.py @@ -37,6 +37,9 @@ EXPECTED = { "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_missing_class_name_bad.claro": [ + "tests/typecheck_missing_class_name_bad.claro:1: CLASS needs a class name. Add a name after CLASS, such as CLASS Player.", + ], "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.", ],