typecheck: diagnose missing class field names
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
# Changelog
|
||||
|
||||
### Diagnose missing class field names
|
||||
|
||||
- `claro typecheck` now explains how to repair a bare `HAS` declaration instead of reporting a confusing missing type for an unnamed field.
|
||||
- Added focused coverage to the complete typecheck diagnostic validation matrix.
|
||||
|
||||
### Diagnose missing method names
|
||||
|
||||
- `claro typecheck` now explains how to repair a bare `TEACH` declaration inside a class instead of describing it as a top-level function.
|
||||
|
||||
@@ -189,7 +189,7 @@ The same declaration check applies to object methods in both syntax styles. For
|
||||
|
||||
Object methods must also have unique names within their class. If `CLASS Player` declares `TEACH show` twice, `claro typecheck` reports `Method Player.show is declared more than once. Give each method a different name.` Rename one method before calling it.
|
||||
|
||||
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 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.` A bare `HAS` reports `Class Player needs a field name. Add a name and type after HAS, such as HAS score NUMBER.` 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 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. A `NEW` declaration must include both a class name and an object name; bare `NEW` reports `NEW needs a class name. Add a class and object name, such as NEW Player player.`, while `NEW Player` reports `NEW Player needs an object name. Add a name after the class, such as NEW Player player.`
|
||||
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.
|
||||
|
||||
@@ -59,7 +59,7 @@ Ready now:
|
||||
- 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, and a missing method name inside `CLASS` names the class and gives a method example
|
||||
- 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 field declarations now reject missing names such as bare `HAS`, 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
|
||||
- object creation now rejects repeated object names with a direct repair hint, so two `NEW Player player` statements cannot silently replace one another during type checking; when a script declares at least one class, a misspelled `NEW` class name gets a direct declaration hint without changing the older permissive no-class form; a missing class name or object name gets a direct example repair; extra words after the object name get a direct repair hint instead of being silently ignored
|
||||
- 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, explicitly typed field assignments, explicitly typed method-body assignments to undeclared fields with a `HAS field TYPE` repair hint in modern and compatibility method syntax, dedicated positive NUMBER/TEXT/YESNO `CHECK TYPE` metadata fixtures, simple and chained object aliases for both field assignments and `CHECK TYPE` metadata (including a chained TEXT-field check), aliased object-method calls including chained aliases in modern `DO` and compatibility `CALL ... WITH` forms (with a dedicated positive modern `DO` chained-alias fixture), field-to-field, compound arithmetic field expressions, and arithmetic/text expression result types, 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 including explicitly typed assignments, numeric and text results from simple arithmetic/text expressions in object-field assignments, plus method-body field assignment and `CHECK TYPE` diagnostics for NUMBER, TEXT, and YESNO fields in modern and compatibility syntax, including compatibility YESNO `CHECK TYPE` metadata coverage, direct method assignments to undeclared fields now produce a beginner-facing missing-field diagnostic when the assigned expression has a known type, and a plain beginner-facing unknown-field diagnostic when the assigned expression is not inferable yet in both modern and compatibility method syntax; method-body `CHECK TYPE` now reports an undeclared bare field with the expected type as a repair hint in both modern and compatibility method syntax, including compatibility `TAKES` / ...; explicitly typed assignments to declared method fields now validate the class-declared field type instead of trusting only the inline type annotation, so a wrong value such as `SET score TEXT "oops"` reports the method and field in the diagnostic
|
||||
|
||||
@@ -64,6 +64,7 @@ Keep declared return types honest: `claro typecheck` now reports friendly diagno
|
||||
8l. Keep object creation understandable: when classes are declared in a script, an unknown `NEW` class name now gets a direct class-name diagnostic and repair hint; the older permissive `NEW` behavior remains when no class declarations are present.
|
||||
|
||||
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.
|
||||
8m.1. Keep class field declarations understandable: a bare `HAS` now gets a direct diagnostic explaining that both a field name and type are required, with a small `HAS score NUMBER` repair example.
|
||||
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
-1
@@ -626,7 +626,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;ci<declared_class_count;ci++) if(ci_eq(declared_classes[ci],name)){ duplicate=1; break; } if(!*name){ printf("%s:%d: CLASS needs a class name. Add a name after CLASS, such as CLASS Player.\n",path,line_no); errs++; current_class[0]=0; } else if(*extra){ printf("%s:%d: Class %s has extra text after its name. Keep only the class name after CLASS.\n",path,line_no,name); errs++; } else if(duplicate){ printf("%s:%d: Class %s is declared more than once. Give each class a different name.\n",path,line_no,name); errs++; } else if(declared_class_count<128) snprintf(declared_classes[declared_class_count++],sizeof(declared_classes[0]),"%s",name); snprintf(current_class,sizeof(current_class),"%s",name); declared_field_count=0; free(cls); }
|
||||
else if(current_class[0]&&!strcmp(up,"HAS")){ const char *pcur=t+3; char *field=unquote_token(&pcur); char *field_type=unquote_token(&pcur); char *name=trim_inplace(field); char *ft=trim_inplace(field_type); char *extra=trim_inplace((char *)pcur); int duplicate=0,fi; for(fi=0;fi<declared_field_count;fi++) if(ci_eq(declared_fields[fi],name)){ duplicate=1; break; } if(duplicate){ printf("%s:%d: Class %s declares field %s more than once. Give each field a different name.\n",path,line_no,current_class,name); errs++; } else if(!*ft){ printf("%s:%d: Class %s field %s is missing a type. Add a type such as NUMBER, TEXT, YESNO, LIST, or MAP after the field name.\n",path,line_no,current_class,name); errs++; } else if(!claro_is_type_word(ft)){ printf("%s:%d: Class %s field %s uses an unknown type %s. Use a Claro type such as NUMBER, TEXT, YESNO, LIST, or MAP.\n",path,line_no,current_class,name,ft); errs++; } else if(*extra){ printf("%s:%d: Class %s field %s has extra text after type %s. Keep only the field name and one type.\n",path,line_no,current_class,name,ft); errs++; } else if(declared_field_count<128) snprintf(declared_fields[declared_field_count++],sizeof(declared_fields[0]),"%s",name); free(field); free(field_type); }
|
||||
else if(current_class[0]&&!strcmp(up,"HAS")){ const char *pcur=t+3; char *field=unquote_token(&pcur); char *field_type=unquote_token(&pcur); char *name=trim_inplace(field); char *ft=trim_inplace(field_type); char *extra=trim_inplace((char *)pcur); int duplicate=0,fi; for(fi=0;fi<declared_field_count;fi++) if(ci_eq(declared_fields[fi],name)){ duplicate=1; break; } if(!*name){ printf("%s:%d: Class %s needs a field name. Add a name and type after HAS, such as HAS score NUMBER.\n",path,line_no,current_class); errs++; } else if(duplicate){ printf("%s:%d: Class %s declares field %s more than once. Give each field a different name.\n",path,line_no,current_class,name); errs++; } else if(!*ft){ printf("%s:%d: Class %s field %s is missing a type. Add a type such as NUMBER, TEXT, YESNO, LIST, or MAP after the field name.\n",path,line_no,current_class,name); errs++; } else if(!claro_is_type_word(ft)){ printf("%s:%d: Class %s field %s uses an unknown type %s. Use a Claro type such as NUMBER, TEXT, YESNO, LIST, or MAP.\n",path,line_no,current_class,name,ft); errs++; } else if(*extra){ printf("%s:%d: Class %s field %s has extra text after type %s. Keep only the field name and one type.\n",path,line_no,current_class,name,ft); errs++; } else if(declared_field_count<128) snprintf(declared_fields[declared_field_count++],sizeof(declared_fields[0]),"%s",name); free(field); free(field_type); }
|
||||
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)){ if(current_class[0]) printf("%s:%d: Method %s needs a method name. Add a name after TEACH, such as TEACH show.\n",path,line_no,current_class); else printf("%s:%d: TEACH needs a function name. Add a name after TEACH, such as TEACH greet.\n",path,line_no); errs++; } if(!fname) fname=xstrdup(""); 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;mi<declared_method_count;mi++) if(ci_eq(declared_methods[mi],full_name)){ duplicate_method=1; break; } if(duplicate_method){ printf("%s:%d: Method %s is declared more than once. Give each method a different name.\n",path,line_no,full_name); errs++; } else if(declared_method_count<128) snprintf(declared_methods[declared_method_count++],sizeof(declared_methods[0]),"%s",full_name); } else { int fi,duplicate_function=0; for(fi=0;fi<declared_function_count;fi++) if(ci_eq(declared_functions[fi],full_name)){ duplicate_function=1; break; } if(duplicate_function){ printf("%s:%d: Function %s is declared more than once. Give each function a different name.\n",path,line_no,full_name); errs++; } else if(declared_function_count<128) snprintf(declared_functions[declared_function_count++],sizeof(declared_functions[0]),"%s",full_name); } { const char *duplicate=duplicate_param_name(params,pcnt); if(duplicate){ printf("%s:%d: %s %s declares parameter %s more than once. Give each parameter a different name.\n",path,line_no,current_class[0]?"Method":"Function",full_name,trim_inplace((char *)duplicate)); errs++; } } { int pi; for(pi=0;pi<pcnt;pi++) type_env_set(&types,params[pi],"ANY"); } current_func[0]=0; current_method[0]=0; return_seen=0; top_level_return_seen=0; nested_blocks=0; conditional_paths_complete=1; in_teach=1; if(current_class[0]) snprintf(current_method,sizeof(current_method),"%s",full_name); if(current_class[0]){ TypeFieldCheck *fc; for(fc=field_checks;fc;fc=fc->next) 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; } }
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
CLASS Player
|
||||
HAS
|
||||
END
|
||||
@@ -40,6 +40,9 @@ EXPECTED = {
|
||||
"tests/typecheck_missing_method_name_bad.claro": [
|
||||
"tests/typecheck_missing_method_name_bad.claro:2: Method Player needs a method name. Add a name after TEACH, such as TEACH show.",
|
||||
],
|
||||
"tests/typecheck_missing_field_name_bad.claro": [
|
||||
"tests/typecheck_missing_field_name_bad.claro:2: Class Player needs a field name. Add a name and type after HAS, such as HAS score NUMBER.",
|
||||
],
|
||||
"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.",
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user