feat: add object field unknown-field diagnostic

This commit is contained in:
Hermes Agent
2026-06-23 03:06:22 -06:00
parent 5914524ad6
commit 5a63e76a44
8 changed files with 42 additions and 5 deletions
+2 -2
View File
@@ -522,7 +522,7 @@ static int param_index_named(char **params,int pcnt,const char *name){ int i; ch
static void collect_teach_param_checks_from_body(TypeParamCheck **checks,FILE *f,const char *func_name,char **params,int pcnt){ char line[4096]; 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; if(!strcmp(iup,"CHECK")&&starts_ci(it,"CHECK TYPE")){ const char *is=find_word_ci(it,"IS"); if(is){ char *expr=substr(it+10,is); char *need=xstrdup(trim_inplace((char*)is+2)); char *en=trim_inplace(expr); int idx=param_index_named(params,pcnt,en); if(idx>=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"))&&current_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,&params,&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; }
static TypeFieldCheck *collect_class_field_type_checks(FILE *f){ TypeFieldCheck *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"))&&current_class[0]){ current_class[0]=0; continue; } if(current_class[0]&&!strcmp(up,"HAS")){ const char *pcur=t+3; char *field=unquote_token(&pcur); char *type=unquote_token(&pcur); add_type_field_check(&checks,current_class,trim_inplace(field),trim_inplace(type)); free(field); free(type); } } 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; if(!f){ fprintf(stderr,"Could not open %s\n",path); return 1; } param_checks=collect_function_param_type_checks(f); field_checks=collect_class_field_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(line)-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,"SET")){ char *name=NULL,*type=NULL,*expr=NULL; const char *got,*old; parse_set_for_typecheck2(t,&name,&type,&expr); if(name&&*trim_inplace(name)){ char *nt=trim_inplace(name); char *tt=type?trim_inplace(type):NULL; got=simple_expr_type(types,expr); old=type_env_get(types,nt); if(tt&&*tt){ if(got&&!type_words_match(tt,got)){ printf("%s:%d: Type mismatch for %s: expected %s, but this value looks like %s.\n",path,line_no,nt,tt,got); errs++; } type_env_set(&types,nt,tt); } else if(old){ if(got&&!type_words_match(old,got)){ if(strchr(nt,'.')) printf("%s:%d: Type mismatch for field %s: expected %s, but this value looks like %s.\n",path,line_no,nt,old,got); else printf("%s:%d: Type mismatch for %s: it was first set as %s, but this value looks like %s.\n",path,line_no,nt,old,got); errs++; } } else if(got) type_env_set(&types,nt,got); else type_env_set(&types,nt,"ANY"); } free(name); free(type); free(expr); }
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; if(!f){ fprintf(stderr,"Could not open %s\n",path); return 1; } param_checks=collect_function_param_type_checks(f); field_checks=collect_class_field_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(line)-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,"SET")){ char *name=NULL,*type=NULL,*expr=NULL; const char *got,*old; parse_set_for_typecheck2(t,&name,&type,&expr); if(name&&*trim_inplace(name)){ char *nt=trim_inplace(name); char *tt=type?trim_inplace(type):NULL; got=simple_expr_type(types,expr); old=type_env_get(types,nt); if(tt&&*tt){ if(got&&!type_words_match(tt,got)){ printf("%s:%d: Type mismatch for %s: expected %s, but this value looks like %s.\n",path,line_no,nt,tt,got); errs++; } type_env_set(&types,nt,tt); } else if(old){ if(got&&!type_words_match(old,got)){ if(strchr(nt,'.')) printf("%s:%d: Type mismatch for field %s: expected %s, but this value looks like %s.\n",path,line_no,nt,old,got); else printf("%s:%d: Type mismatch for %s: it was first set as %s, but this value looks like %s.\n",path,line_no,nt,old,got); errs++; } } else if(strchr(nt,'.')){ char objname[128],fieldname[128]; char *dot=strchr(nt,'.'); const char *objtype; snprintf(objname,sizeof(objname),"%.*s",(int)(dot-nt),nt); snprintf(fieldname,sizeof(fieldname),"%s",dot+1); objtype=type_env_get(types,objname); if(objtype&&starts_ci(objtype,"OBJECT:")){ const char *cls=objtype+7; if(!type_field_check_for(field_checks,cls,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,cls,fieldname,fieldname,got?got:"ANY"); errs++; } } else if(got) type_env_set(&types,nt,got); else type_env_set(&types,nt,"ANY"); } else if(got) type_env_set(&types,nt,got); else type_env_set(&types,nt,"ANY"); } free(name); free(type); free(expr); }
else if(!strcmp(up,"ADD")){ const char *to=find_word_ci(t,"TO"); if(to){ char *expr=substr(t+3,to); char *name=xstrdup(trim_inplace((char*)to+2)); const char *listtype=type_env_get(types,name); const char *need=container_member_type(listtype,"LIST"); const char *got=simple_expr_type(types,expr); if(need&&got&&!type_words_match(need,got)){ printf("%s:%d: Type mismatch for list %s: expected %s item, but this value looks like %s.\n",path,line_no,name,need,got); errs++; } free(expr); free(name); } }
else if(!strcmp(up,"PUT")){ const char *key=find_word_ci(t,"KEY"), *val=find_word_ci(t,"VALUE"); if(key&&val){ char *name=substr(t+3,key); char *expr=xstrdup(trim_inplace((char*)val+5)); char *nt=trim_inplace(name); const char *maptype=type_env_get(types,nt); const char *need=container_member_type(maptype,"MAP"); const char *got=simple_expr_type(types,expr); if(need&&got&&!type_words_match(need,got)){ printf("%s:%d: Type mismatch for map %s: expected %s value, but this value looks like %s.\n",path,line_no,nt,need,got); errs++; } free(name); free(expr); } }
else if(!strcmp(up,"ASK")){ const char *as=find_word_ci(t,"AS"); char *name=NULL,*type=NULL; if(as){ const char *pcur=as+2; name=unquote_token(&pcur); { char *rest=xstrdup(trim_inplace((char*)pcur)); if(claro_is_type_word(rest)) type=rest; else free(rest); } } else { const char *pcur=t+3; char *prompt=unquote_token(&pcur); free(prompt); name=unquote_token(&pcur); { char *rest=xstrdup(trim_inplace((char*)pcur)); if(claro_is_type_word(rest)) type=rest; else free(rest); } } if(name&&*trim_inplace(name)) type_env_set(&types,trim_inplace(name),type?type:"TEXT"); free(name); free(type); }
@@ -608,6 +608,6 @@ static void print_help(void){
static int show_examples(void){ const char *items[]={"hello.claro","name_input.claro","quiz.claro","calculator.claro","guessing_game.claro","shopping_list.claro","save_and_load_file.claro","simple_functions.claro","text_and_lists.claro","text_polish.claro","practical_scripting.claro","type_hardening.claro","typed_ask.claro","objects_classes.claro","networking.claro",NULL}; int i; printf("Included beginner examples:\n"); for(i=0;items[i];i++) printf(" examples/%s\n",items[i]); printf("\nRun one with: claro examples/hello.claro\n"); return 0; }
static int file_exists_simple(const char *path){ FILE *f=fopen(path,"rb"); if(f){ fclose(f); return 1; } return 0; }
static int run_doctor(void){ int ok=1; const char *files[]={"src/claro.c","README.md","assets/Claro_Logo.jpg","lessons/01_hello.claro","examples/hello.claro","tests/01_hello.claro",NULL}; int i; printf("%s\n",CLARO_VERSION); printf("Doctor check:\n"); for(i=0;files[i];i++){ int has=file_exists_simple(files[i]); printf(" %s %s\n",has?"OK":"MISSING",files[i]); if(!has) ok=0; } printf("%s\n",ok?"Claro folder looks ready.":"Some Claro files are missing."); return ok?0:1; }
static int run_validate(void){ int fails=0, i; const char *checks[]={"lessons/01_hello.claro","lessons/02_ask_name.claro","lessons/03_variables.claro","lessons/04_math.claro","lessons/05_if_else.claro","lessons/06_loops.claro","lessons/07_lists.claro","lessons/08_functions.claro","lessons/09_files.claro","lessons/10_final_quiz.claro","examples/hello.claro","examples/name_input.claro","examples/quiz.claro","examples/calculator.claro","examples/guessing_game.claro","examples/shopping_list.claro","examples/save_and_load_file.claro","examples/simple_functions.claro","examples/text_and_lists.claro","examples/text_polish.claro","examples/practical_scripting.claro","examples/type_hardening.claro","examples/objects_classes.claro","examples/networking.claro",NULL}; printf("%s\n",CLARO_VERSION); printf("Stable package validation:\n"); fails+=run_doctor(); fails+=run_tests(); printf("Checking lessons and main examples:\n"); for(i=0;checks[i];i++){ printf(" %s\n",checks[i]); fails+=check_file(checks[i]); } fails+=typecheck_file("tests/typecheck_good.claro"); fails+=typecheck_file("tests/typecheck_container_good.claro"); fails+=typecheck_file("tests/typecheck_function_good.claro"); fails+=typecheck_file("tests/typecheck_function_multi_good.claro"); fails+=typecheck_file("tests/typecheck_method_good.claro"); fails+=typecheck_file("tests/typecheck_object_field_good.claro"); fails+=typecheck_file("tests/typecheck_object_field_text_good.claro"); fails+=typecheck_file("tests/typecheck_object_field_yesno_good.claro"); fails+=typecheck_file("tests/37_object_field_types.claro"); if(typecheck_file("tests/typecheck_bad.claro")==0) fails++; if(typecheck_file("tests/typecheck_container_bad.claro")==0) fails++; if(typecheck_file("tests/typecheck_function_bad.claro")==0) fails++; if(typecheck_file("tests/typecheck_function_multi_bad.claro")==0) fails++; if(typecheck_file("tests/typecheck_method_bad.claro")==0) fails++; if(typecheck_file("tests/typecheck_object_field_bad.claro")==0) fails++; if(typecheck_file("tests/typecheck_object_field_text_bad.claro")==0) fails++; if(typecheck_file("tests/typecheck_object_field_yesno_bad.claro")==0) fails++; printf("%s\n",fails?"Validation found problems.":"Validation passed. Claro v1.18.26 foundation checks are ready for use."); return fails?1:0; }
static int run_validate(void){ int fails=0, i; const char *checks[]={"lessons/01_hello.claro","lessons/02_ask_name.claro","lessons/03_variables.claro","lessons/04_math.claro","lessons/05_if_else.claro","lessons/06_loops.claro","lessons/07_lists.claro","lessons/08_functions.claro","lessons/09_files.claro","lessons/10_final_quiz.claro","examples/hello.claro","examples/name_input.claro","examples/quiz.claro","examples/calculator.claro","examples/guessing_game.claro","examples/shopping_list.claro","examples/save_and_load_file.claro","examples/simple_functions.claro","examples/text_and_lists.claro","examples/text_polish.claro","examples/practical_scripting.claro","examples/type_hardening.claro","examples/objects_classes.claro","examples/networking.claro",NULL}; printf("%s\n",CLARO_VERSION); printf("Stable package validation:\n"); fails+=run_doctor(); fails+=run_tests(); printf("Checking lessons and main examples:\n"); for(i=0;checks[i];i++){ printf(" %s\n",checks[i]); fails+=check_file(checks[i]); } fails+=typecheck_file("tests/typecheck_good.claro"); fails+=typecheck_file("tests/typecheck_container_good.claro"); fails+=typecheck_file("tests/typecheck_function_good.claro"); fails+=typecheck_file("tests/typecheck_function_multi_good.claro"); fails+=typecheck_file("tests/typecheck_method_good.claro"); fails+=typecheck_file("tests/typecheck_object_field_good.claro"); fails+=typecheck_file("tests/typecheck_object_field_text_good.claro"); fails+=typecheck_file("tests/typecheck_object_field_yesno_good.claro"); fails+=typecheck_file("tests/37_object_field_types.claro"); if(typecheck_file("tests/typecheck_bad.claro")==0) fails++; if(typecheck_file("tests/typecheck_container_bad.claro")==0) fails++; if(typecheck_file("tests/typecheck_function_bad.claro")==0) fails++; if(typecheck_file("tests/typecheck_function_multi_bad.claro")==0) fails++; if(typecheck_file("tests/typecheck_method_bad.claro")==0) fails++; if(typecheck_file("tests/typecheck_object_field_bad.claro")==0) fails++; if(typecheck_file("tests/typecheck_object_field_text_bad.claro")==0) fails++; if(typecheck_file("tests/typecheck_object_field_yesno_bad.claro")==0) fails++; if(typecheck_file("tests/typecheck_object_field_unknown_bad.claro")==0) fails++; printf("%s\n",fails?"Validation found problems.":"Validation passed. Claro v1.18.26 foundation checks are ready for use."); return fails?1:0; }
int main(int argc,char **argv){ int arg=1, trace=0; if(argc<2){ print_help(); return 0;} if(strcmp(argv[arg],"--trace")==0||strcmp(argv[arg],"trace")==0){ trace=1; arg++; } if(arg>=argc) return 0; if(strcmp(argv[arg],"help")==0||strcmp(argv[arg],"--help")==0){ print_help(); return 0; } if(strcmp(argv[arg],"test")==0) return run_tests(); if(strcmp(argv[arg],"repl")==0) return run_repl(); if(strcmp(argv[arg],"examples")==0) return show_examples(); if(strcmp(argv[arg],"doctor")==0) return run_doctor(); if(strcmp(argv[arg],"validate")==0) return run_validate(); if(strcmp(argv[arg],"package")==0) return run_package_cmd(argc,argv,arg+1); if(strcmp(argv[arg],"ide")==0) return print_ide_info(); if(strcmp(argv[arg],"new")==0&&arg+1<argc) return create_new_project(argv[arg+1]); if(strcmp(argv[arg],"fmt")==0&&arg+1<argc) return fmt_file(argv[arg+1]); if(strcmp(argv[arg],"check")==0&&arg+1<argc) return check_file(argv[arg+1]); if(strcmp(argv[arg],"typecheck")==0&&arg+1<argc) return typecheck_file(argv[arg+1]); if(strcmp(argv[arg],"version")==0||strcmp(argv[arg],"--version")==0){ puts(CLARO_VERSION); return 0; } if(strcmp(argv[arg],"run")==0){ arg++; if(arg>=argc){ Runtime rt; int rc; char *mainfile=project_value("main"); if(!mainfile||!*mainfile){ if(mainfile) free(mainfile); mainfile=xstrdup("main.claro"); } rt_init(&rt); rt.trace=trace; rt.script_argc=0; rt.script_argv=NULL; rc=run_file(&rt,mainfile); free(mainfile); return rc; } } { Runtime rt; int rc; rt_init(&rt); rt.trace=trace; rt.script_argc=argc-arg-1; rt.script_argv=argv+arg+1; rc=run_file(&rt,argv[arg]); return rc; } }