typecheck: explain text subtraction operands

This commit is contained in:
Hermes Agent
2026-09-07 16:44:18 +00:00
parent f88b27ae8c
commit 2e3b1c725f
5 changed files with 7 additions and 6 deletions
+2 -2
View File
@@ -231,10 +231,10 @@ Type mismatch for field alias.score: expected NUMBER, but this value looks like
The same check follows simple arithmetic and text-concatenation expressions. For example, `SET player.score player.name + 1` reports that the value looks like `TEXT`, rather than silently accepting an expression whose result cannot fit the `NUMBER` field. The same check follows simple arithmetic and text-concatenation expressions. For example, `SET player.score player.name + 1` reports that the value looks like `TEXT`, rather than silently accepting an expression whose result cannot fit the `NUMBER` field.
The checker also identifies text operands in other arithmetic expressions. For example, `SET player.score player.score - player.name` reports the text result instead of silently treating an invalid numeric subtraction as unknown: The checker also identifies text operands in other arithmetic expressions. For example, `SET player.score player.score - player.name` now explains the operator rule and names the operand, rather than silently treating an invalid numeric subtraction as unknown:
```text ```text
Type mismatch for field player.score: expected NUMBER, but this value looks like TEXT. Type mismatch for field player.score: subtraction needs NUMBER values, but player.name looks like TEXT.
``` ```
TEXT-valued and YESNO-valued field-name mistakes are validated too: TEXT-valued and YESNO-valued field-name mistakes are validated too:
+1 -1
View File
@@ -57,7 +57,7 @@ Ready now:
Still needed: Still needed:
- Keep the focused typecheck validator's fixture list complete as new positive and negative examples are added. - Keep the focused typecheck validator's fixture list complete as new positive and negative examples are added.
- The expression checker now carries a known TEXT operand through all arithmetic operators so object-field diagnostics do not hide subtraction, multiplication, or division mistakes; focused numeric-division and numeric-multiplication positives plus text-operand subtraction, multiplication, and division negatives protect this behavior, while operator-specific diagnostics remain planned. - The expression checker now carries a known TEXT operand through all arithmetic operators so object-field diagnostics do not hide subtraction, multiplication, or division mistakes; focused numeric-division and numeric-multiplication positives plus text-operand subtraction, multiplication, and division negatives protect this behavior. Subtraction now also names the text operand and explains that subtraction needs NUMBER values; multiplication/division-specific guidance remains planned.
- richer typed function signatures and return values - richer typed function signatures and return values
- type checking through branches and loops - type checking through branches and loops
- richer object field type checking beyond simple direct assignments - richer object field type checking beyond simple direct assignments
+1 -1
View File
@@ -36,7 +36,7 @@ See `CURRENT_STATUS.md` for the detailed feature matrix.
3b. Keep package security validation in Forgejo/Gitea CI so unsafe names, exact manifest-version/name/version/checksum mismatches, missing manifests, duplicate project manifest versions and packages, duplicate lock packages, duplicate package manifest names, versions, and checksums, and lockfile errors remain release blockers. 3b. Keep package security validation in Forgejo/Gitea CI so unsafe names, exact manifest-version/name/version/checksum mismatches, missing manifests, duplicate project manifest versions and packages, duplicate lock packages, duplicate package manifest names, versions, and checksums, and lockfile errors remain release blockers.
4. Add small examples for each foundation feature before adding bigger syntax. The object-field foundation now includes positive and negative validation for field expressions such as `SET player.score player.name`, `SET player.score player.score + 1`, `SET player.name player.score + 1`, explicitly typed field assignments, simple and chained aliases such as `SET alias player`, `SET backup alias`, followed by `SET backup.score ...` or `CHECK TYPE backup.score IS ...`, chained aliases in object-method calls through both modern `DO` and compatibility `CALL ... WITH` forms (including a dedicated positive modern `DO` fixture), and arithmetic/text expression mismatches; typed containers also accept a map with nested type metadata when it is added to a `LIST OF MAP`; broader alias/control-flow checking remains planned. 4. Add small examples for each foundation feature before adding bigger syntax. The object-field foundation now includes positive and negative validation for field expressions such as `SET player.score player.name`, `SET player.score player.score + 1`, `SET player.name player.score + 1`, explicitly typed field assignments, simple and chained aliases such as `SET alias player`, `SET backup alias`, followed by `SET backup.score ...` or `CHECK TYPE backup.score IS ...`, chained aliases in object-method calls through both modern `DO` and compatibility `CALL ... WITH` forms (including a dedicated positive modern `DO` fixture), and arithmetic/text expression mismatches; typed containers also accept a map with nested type metadata when it is added to a `LIST OF MAP`; broader alias/control-flow checking remains planned.
5. Keep the focused typecheck validator complete: every `typecheck_*.claro` fixture, including positive fixtures, must be exercised by release validation. 5. Keep the focused typecheck validator complete: every `typecheck_*.claro` fixture, including positive fixtures, must be exercised by release validation.
6. Continue narrowing expression diagnostics: known TEXT operands now remain visible through non-concatenation arithmetic, with focused numeric-division and numeric-multiplication positives plus text-operand subtraction, multiplication, and division coverage; operator-specific guidance is still future work. 6. Continue narrowing expression diagnostics: known TEXT operands now remain visible through non-concatenation arithmetic, with focused numeric-division and numeric-multiplication positives plus text-operand subtraction, multiplication, and division coverage. Subtraction now gives operator-specific guidance naming the text operand; matching multiplication and division guidance remains future work.
## Complete-platform milestones ## Complete-platform milestones
+2 -1
View File
@@ -555,6 +555,7 @@ static const char *simple_expr_type(Var *types,const char *expr){
if(i>0 && t[i]==0) return type_env_get(types,t); if(i>0 && t[i]==0) return type_env_get(types,t);
return NULL; return NULL;
} }
static const char *text_operand_for_operator(Var *types,const char *expr,char wanted){ static char operand[256]; char tmp[512]; char *t; size_t i; int in_string=0; if(!expr) return NULL; strncpy(tmp,expr,sizeof(tmp)-1); tmp[sizeof(tmp)-1]=0; t=trim_inplace(tmp); for(i=0;t[i];i++){ char *left,*right; const char *lt,*rt; if(t[i]=='"') in_string=!in_string; if(!in_string&&t[i]==wanted){ left=substr(t,t+i); right=xstrdup(t+i+1); lt=simple_expr_type(types,left); rt=simple_expr_type(types,right); if(lt&&ci_eq(lt,"TEXT")){ snprintf(operand,sizeof(operand),"%s",trim_inplace(left)); free(left); free(right); return operand; } if(rt&&ci_eq(rt,"TEXT")){ snprintf(operand,sizeof(operand),"%s",trim_inplace(right)); free(left); free(right); return operand; } free(left); free(right); } } return NULL; }
static int type_words_match(const char *need,const char *got){ if(!need||!*need||ci_eq(need,"ANY")) return 1; if(!got||!*got) return 1; if(ci_eq(need,got)) return 1; if((ci_eq(need,"LIST")||ci_eq(need,"MAP")) && starts_ci(got,need)) return 1; if(starts_ci(need,"LIST OF") && ci_eq(got,"LIST")) return 1; if(starts_ci(need,"MAP OF") && ci_eq(got,"MAP")) return 1; if((ci_eq(need,"YESNO")||ci_eq(need,"BOOL")||ci_eq(need,"BOOLEAN")) && (ci_eq(got,"YESNO")||ci_eq(got,"BOOL")||ci_eq(got,"BOOLEAN"))) return 1; if(ci_eq(need,"OBJECT") && (ci_eq(got,"MAP")||starts_ci(got,"OBJECT:"))) return 1; return 0; } static int type_words_match(const char *need,const char *got){ if(!need||!*need||ci_eq(need,"ANY")) return 1; if(!got||!*got) return 1; if(ci_eq(need,got)) return 1; if((ci_eq(need,"LIST")||ci_eq(need,"MAP")) && starts_ci(got,need)) return 1; if(starts_ci(need,"LIST OF") && ci_eq(got,"LIST")) return 1; if(starts_ci(need,"MAP OF") && ci_eq(got,"MAP")) return 1; if((ci_eq(need,"YESNO")||ci_eq(need,"BOOL")||ci_eq(need,"BOOLEAN")) && (ci_eq(got,"YESNO")||ci_eq(got,"BOOL")||ci_eq(got,"BOOLEAN"))) return 1; if(ci_eq(need,"OBJECT") && (ci_eq(got,"MAP")||starts_ci(got,"OBJECT:"))) return 1; return 0; }
static const char *container_member_type(const char *type,const char *kind){ const char *p; static char buf[128]; if(!type||!kind) return NULL; if(!starts_ci(type,kind)) return NULL; p=type+strlen(kind); while(*p&&isspace((unsigned char)*p)) p++; if(!starts_ci(p,"OF")) return NULL; p+=2; while(*p&&isspace((unsigned char)*p)) p++; if(!*p) return NULL; snprintf(buf,sizeof(buf),"%s",p); return trim_inplace(buf); } static const char *container_member_type(const char *type,const char *kind){ const char *p; static char buf[128]; if(!type||!kind) return NULL; if(!starts_ci(type,kind)) return NULL; p=type+strlen(kind); while(*p&&isspace((unsigned char)*p)) p++; if(!starts_ci(p,"OF")) return NULL; p+=2; while(*p&&isspace((unsigned char)*p)) p++; if(!*p) return NULL; snprintf(buf,sizeof(buf),"%s",p); return trim_inplace(buf); }
static void parse_set_for_typecheck2(const char *t,char **name,char **type,char **expr){ const char *rest=t+3; const char *to=find_word_ci(rest,"TO"); const char *as=find_word_ci(rest,"AS"); *name=NULL; *type=NULL; *expr=NULL; if(as&&to&&as<to){ *name=substr(rest,as); *type=substr(as+2,to); *expr=xstrdup(to+2); return; } if(to){ char *before=substr(rest,to); const char *pcur=before; char *var=unquote_token(&pcur); char *maybe=unquote_token(&pcur); char *mt=trim_inplace(maybe); *name=xstrdup(trim_inplace(var)); if(claro_is_type_word(mt)) *type=xstrdup(mt); *expr=xstrdup(to+2); free(before); free(var); free(maybe); return; } { const char *pcur=rest; char *var=unquote_token(&pcur); const char *after_var=pcur; char *maybe=unquote_token(&pcur); char *mt=trim_inplace(maybe); *name=xstrdup(trim_inplace(var)); if(claro_is_type_word(mt)){ *type=xstrdup(mt); *expr=xstrdup(trim_inplace((char*)pcur)); } else { *expr=xstrdup(trim_inplace((char*)after_var)); } free(var); free(maybe); } } static void parse_set_for_typecheck2(const char *t,char **name,char **type,char **expr){ const char *rest=t+3; const char *to=find_word_ci(rest,"TO"); const char *as=find_word_ci(rest,"AS"); *name=NULL; *type=NULL; *expr=NULL; if(as&&to&&as<to){ *name=substr(rest,as); *type=substr(as+2,to); *expr=xstrdup(to+2); return; } if(to){ char *before=substr(rest,to); const char *pcur=before; char *var=unquote_token(&pcur); char *maybe=unquote_token(&pcur); char *mt=trim_inplace(maybe); *name=xstrdup(trim_inplace(var)); if(claro_is_type_word(mt)) *type=xstrdup(mt); *expr=xstrdup(to+2); free(before); free(var); free(maybe); return; } { const char *pcur=rest; char *var=unquote_token(&pcur); const char *after_var=pcur; char *maybe=unquote_token(&pcur); char *mt=trim_inplace(maybe); *name=xstrdup(trim_inplace(var)); if(claro_is_type_word(mt)){ *type=xstrdup(mt); *expr=xstrdup(trim_inplace((char*)pcur)); } else { *expr=xstrdup(trim_inplace((char*)after_var)); } free(var); free(maybe); } }
@@ -618,7 +619,7 @@ static TypeFieldCheck *collect_class_field_type_checks(FILE *f){
return checks; 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"))&&current_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,&params,&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 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"))&&current_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,&params,&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; 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); 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(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(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 *declared=type_field_check_for(field_checks,objtype+7,fieldname); if(declared&&got&&!type_words_match(declared,got)){ printf("%s:%d: Type mismatch for field %s: expected %s, but this value looks like %s.\n",path,line_no,nt,declared,got); errs++; } else 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++; } } else 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++; } } else 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)){ if(got&&!strchr(expr,'+')&&!strchr(expr,'-')&&!strchr(expr,'*')&&!strchr(expr,'/')) 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); else printf("%s:%d: Object %s has no field %s. Check the field name or add the field to the class with the right type.\n",path,line_no,cls,fieldname); errs++; } else { const char *fieldtype=type_field_check_for(field_checks,cls,fieldname); if(got&&!type_words_match(fieldtype,got)){ printf("%s:%d: Type mismatch for field %s.%s: expected %s, but this value looks like %s.\n",path,line_no,objname,fieldname,fieldtype,got); errs++; } } } else { printf("%s:%d: Object %s is not known yet. Create it with NEW ClassName %s before setting %s.\n",path,line_no,objname,objname,nt); 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; TypeMethodCheck *method_checks=NULL; TypeArityCheck *arity_checks=NULL; 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); 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(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(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 *declared=type_field_check_for(field_checks,objtype+7,fieldname); if(declared&&got&&!type_words_match(declared,got)){ const char *text_operand=(ci_eq(declared,"NUMBER")&&ci_eq(got,"TEXT"))?text_operand_for_operator(types,expr,'-'):NULL; if(text_operand){ printf("%s:%d: Type mismatch for field %s: subtraction needs NUMBER values, but %s looks like TEXT.\n",path,line_no,nt,text_operand); } else { printf("%s:%d: Type mismatch for field %s: expected %s, but this value looks like %s.\n",path,line_no,nt,declared,got); } errs++; } else 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++; } } else 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++; } } else 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,'.')){ const char *text_operand=(ci_eq(old,"NUMBER")&&ci_eq(got,"TEXT"))?text_operand_for_operator(types,expr,'-'):NULL; if(text_operand) printf("%s:%d: Type mismatch for field %s: subtraction needs NUMBER values, but %s looks like TEXT.\n",path,line_no,nt,text_operand); else 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)){ if(got&&!strchr(expr,'+')&&!strchr(expr,'-')&&!strchr(expr,'*')&&!strchr(expr,'/')) 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); else printf("%s:%d: Object %s has no field %s. Check the field name or add the field to the class with the right type.\n",path,line_no,cls,fieldname); errs++; } else { const char *fieldtype=type_field_check_for(field_checks,cls,fieldname); if(got&&!type_words_match(fieldtype,got)){ printf("%s:%d: Type mismatch for field %s.%s: expected %s, but this value looks like %s.\n",path,line_no,objname,fieldname,fieldtype,got); errs++; } } } else { printf("%s:%d: Object %s is not known yet. Create it with NEW ClassName %s before setting %s.\n",path,line_no,objname,objname,nt); errs++; } } 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,"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,"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); } 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); }
+1 -1
View File
@@ -111,7 +111,7 @@ EXPECTED = {
"tests/typecheck_object_field_compound_bad.claro:8: Type mismatch for field player.score: expected NUMBER, but this value looks like TEXT.", "tests/typecheck_object_field_compound_bad.claro:8: Type mismatch for field player.score: expected NUMBER, but this value looks like TEXT.",
], ],
"tests/typecheck_object_field_subtraction_bad.claro": [ "tests/typecheck_object_field_subtraction_bad.claro": [
"tests/typecheck_object_field_subtraction_bad.claro:8: Type mismatch for field player.score: expected NUMBER, but this value looks like TEXT.", "tests/typecheck_object_field_subtraction_bad.claro:8: Type mismatch for field player.score: subtraction needs NUMBER values, but player.name looks like TEXT.",
], ],
"tests/typecheck_object_field_multiplication_bad.claro": [ "tests/typecheck_object_field_multiplication_bad.claro": [
"tests/typecheck_object_field_multiplication_bad.claro:8: Type mismatch for field player.score: expected NUMBER, but this value looks like TEXT.", "tests/typecheck_object_field_multiplication_bad.claro:8: Type mismatch for field player.score: expected NUMBER, but this value looks like TEXT.",