diff --git a/docs/ADVANCED_STATIC_TYPING.md b/docs/ADVANCED_STATIC_TYPING.md index b341ce4..5560810 100644 --- a/docs/ADVANCED_STATIC_TYPING.md +++ b/docs/ADVANCED_STATIC_TYPING.md @@ -58,6 +58,22 @@ This is accepted with `Type check OK`. More complex nested container operations ## Complete conditional returns +### Return expressions that need more information + +When a typed function or method returns a name or expression whose type the focused checker cannot infer yet, `claro typecheck` reports that clearly instead of silently accepting it: + +```claro +TEACH square amount RETURNS NUMBER + RETURN missing +END +``` + +```text +Function square's return value could not be understood yet. Use a NUMBER expression after RETURN. +``` + +Use a known typed value, such as a parameter checked with `CHECK TYPE`, while broader expression inference remains future work. + Return declarations accept the same case-insensitive keyword style as the rest of Claro. For example, `returns NUMBER` still enables return checking: ```claro diff --git a/docs/CURRENT_STATUS.md b/docs/CURRENT_STATUS.md index 28d6fc6..1ad9921 100644 --- a/docs/CURRENT_STATUS.md +++ b/docs/CURRENT_STATUS.md @@ -53,7 +53,7 @@ Ready now: - `TYPE OF` and `CHECK TYPE` - `CHECK TYPE` rejects unknown expected type names with a beginner-facing list of supported types - typed list/map checks through `claro typecheck`, including a nested `LIST OF MAP` insertion example -- simple function and object-method return checks with `TEACH name ... RETURNS TYPE`, including learner-facing diagnostics for unknown declared return types, mismatched `RETURN` expressions, empty `RETURN` statements, declarations that contain no `RETURN`, and declarations whose only return is inside an incomplete conditional; complete `IF`/`ELSE` return branches are accepted, including nested complete conditionals in functions and methods, with focused validation for modern and compatibility function syntax, compatibility method syntax, and incomplete conditional coverage for methods; `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; typed methods can also return class-declared fields by simple name, with positive and mismatch coverage +- 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, empty `RETURN` statements, declarations that contain no `RETURN`, and declarations whose only return is inside an incomplete conditional; complete `IF`/`ELSE` return branches are accepted, including nested complete conditionals in functions and methods, with focused validation for modern and compatibility function syntax, compatibility method syntax, and incomplete conditional coverage for methods; `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; typed methods can also return class-declared fields by simple name, with positive and mismatch coverage - 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 WITH ...` 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 - 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, 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 a plain beginner-facing unknown-field diagnostic when the assigned expression type is not inferable yet diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index dd84176..6c196e5 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -39,7 +39,7 @@ See `CURRENT_STATUS.md` for the detailed feature matrix. 6. Continue narrowing expression diagnostics: known TEXT operands now remain visible through arithmetic, with focused numeric-subtraction, numeric-division, and numeric-multiplication positives plus text-operand addition, subtraction, multiplication, and division coverage. Text concatenation into TEXT fields is covered in both operand orders, including field-to-field concatenation. Each numeric operator gives specific guidance naming the text operand. `CHECK TYPE` also rejects misspelled expected type names before comparing values. - Keep IDE metadata aligned with the current beginner syntax: typed-language keywords such as `RETURNS`, `CHECK`, and `TYPE` are now included in the metadata used by editor helpers. - Keep compatibility-call coverage aligned with modern calls: empty `CALL object.method WITH` forms now have a focused missing-argument diagnostic fixture alongside the modern `DO object.method` case. -Keep declared return types honest: `claro typecheck` now reports friendly diagnostics for unknown declared return types, mismatched and empty return expressions, declarations with no `RETURN`, and incomplete conditional branches, when a simple function or object method declares `RETURNS TYPE`; the declaration keyword is case-insensitive like other Claro keywords, with positive coverage in modern and compatibility function forms, including lowercase compatibility declarations and lowercase compatibility method declarations, plus a lowercase compatibility-method mismatch fixture. Complete `IF`/`ELSE` return branches, including nested complete conditionals in functions and methods, are accepted, and modern and compatibility function syntax plus compatibility method syntax are covered by focused fixtures. `CHECK TYPE` parameter metadata also informs arithmetic return-expression checking, including object methods; typed methods can also return class-declared fields by simple name, with positive and mismatch fixtures. Full path-sensitive analysis across nested conditionals and loops remains planned. +Keep declared return types honest: `claro typecheck` now reports friendly diagnostics for unknown declared return types, mismatched, not-yet-inferable, and empty return expressions, declarations with no `RETURN`, and incomplete conditional branches, when a simple function or object method declares `RETURNS TYPE`; the declaration keyword is case-insensitive like other Claro keywords, with positive coverage in modern and compatibility function forms, including lowercase compatibility declarations and lowercase compatibility method declarations, plus a lowercase compatibility-method mismatch fixture. Complete `IF`/`ELSE` return branches, including nested complete conditionals in functions and methods, are accepted, and modern and compatibility function syntax plus compatibility method syntax are covered by focused fixtures. `CHECK TYPE` parameter metadata also informs arithmetic return-expression checking, including object methods; typed methods can also return class-declared fields by simple name, with positive and mismatch fixtures. Full path-sensitive analysis across nested conditionals and loops remains planned. ## Complete-platform milestones diff --git a/src/claro.c b/src/claro.c index fcc8649..3734e12 100644 --- a/src/claro.c +++ b/src/claro.c @@ -556,7 +556,7 @@ static const char *simple_expr_type(Var *types,const char *expr){ 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")||ci_eq(got,"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 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&&asnext) 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; strncpy(teachline,t,sizeof(teachline)-1); teachline[sizeof(teachline)-1]=0; parse_teach_parts(teachline,&fname,¶ms,&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); { int pi; for(pi=0;pinext) 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; } else current_class[0]=0; } - else if(!strcmp(up,"RETURN")&¤t_func[0]){ const char *need=type_return_check_for(return_checks,current_func); char *return_expr=trim_inplace(t+6); const char *got=simple_expr_type(types,return_expr); return_seen=1; if(nested_blocks==0) top_level_return_seen=1; else if(nested_blocks<=64) branch_returned[nested_blocks-1]=1; if(need&&!*return_expr){ printf("%s:%d: %s %s needs a %s value after RETURN. Add a %s expression.\n",path,line_no,strchr(current_func,'.')?"Method":"Function",current_func,need,need); errs++; } else if(need&&got&&!type_words_match(need,got)){ printf("%s:%d: Type mismatch for return from %s: expected %s, but this value looks like %s.\n",path,line_no,current_func,need,got); errs++; } } + else if(!strcmp(up,"RETURN")&¤t_func[0]){ const char *need=type_return_check_for(return_checks,current_func); char *return_expr=trim_inplace(t+6); const char *got=simple_expr_type(types,return_expr); return_seen=1; if(nested_blocks==0) top_level_return_seen=1; else if(nested_blocks<=64) branch_returned[nested_blocks-1]=1; if(need&&!*return_expr){ printf("%s:%d: %s %s needs a %s value after RETURN. Add a %s expression.\n",path,line_no,strchr(current_func,'.')?"Method":"Function",current_func,need,need); errs++; } else if(need&&!got){ printf("%s:%d: %s %s's return value could not be understood yet. Use a %s expression after RETURN.\n",path,line_no,strchr(current_func,'.')?"Method":"Function",current_func,need); errs++; } else if(need&&got&&!type_words_match(need,got)){ printf("%s:%d: Type mismatch for return from %s: expected %s, but this value looks like %s.\n",path,line_no,current_func,need,got); errs++; } } else 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)){ char text_operator=(ci_eq(declared,"NUMBER")&&ci_eq(got,"TEXT"))?(text_operand_for_operator(types,expr,'+')?'+':(text_operand_for_operator(types,expr,'-')?'-':(text_operand_for_operator(types,expr,'*')?'*':(text_operand_for_operator(types,expr,'/')?'/':0)))):0; const char *text_operand=text_operator?text_operand_for_operator(types,expr,text_operator):NULL; if(text_operand){ printf("%s:%d: Type mismatch for field %s: %s needs NUMBER values, but %s looks like TEXT.\n",path,line_no,nt,text_operator=='+'?"addition":(text_operator=='-'?"subtraction":(text_operator=='*'?"multiplication":"division")),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(!declared){ 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,objtype+7,fieldname,fieldname,tt); 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,'.')){ char text_operator=(ci_eq(old,"NUMBER")&&ci_eq(got,"TEXT"))?(text_operand_for_operator(types,expr,'+')?'+':(text_operand_for_operator(types,expr,'-')?'-':(text_operand_for_operator(types,expr,'*')?'*':(text_operand_for_operator(types,expr,'/')?'/':0)))):0; const char *text_operand=text_operator?text_operand_for_operator(types,expr,text_operator):NULL; if(text_operand) printf("%s:%d: Type mismatch for field %s: %s needs NUMBER values, but %s looks like TEXT.\n",path,line_no,nt,text_operator=='+'?"addition":(text_operator=='-'?"subtraction":(text_operator=='*'?"multiplication":"division")),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,"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); } } diff --git a/tests/test_validate_typecheck_diagnostics.py b/tests/test_validate_typecheck_diagnostics.py index 6d2b170..48e4b1a 100644 --- a/tests/test_validate_typecheck_diagnostics.py +++ b/tests/test_validate_typecheck_diagnostics.py @@ -49,6 +49,16 @@ class ValidateTypecheckDiagnosticsTests(unittest.TestCase): self.assertIn("tests/typecheck_function_return_bad.claro", MODULE.EXPECTED) self.assertIn("tests/typecheck_function_return_good.claro", MODULE.EXPECTED_OK) + def test_includes_unknown_function_return_expression_fixtures(self): + self.assertIn( + "tests/typecheck_function_unknown_return_expression_bad.claro", + MODULE.EXPECTED, + ) + self.assertIn( + "tests/typecheck_function_unknown_return_expression_good.claro", + MODULE.EXPECTED_OK, + ) + def test_includes_lowercase_compatibility_return_success_fixture(self): self.assertIn( "tests/typecheck_function_lowercase_return_good.claro", diff --git a/tests/typecheck_function_unknown_return_expression_bad.claro b/tests/typecheck_function_unknown_return_expression_bad.claro new file mode 100644 index 0000000..3214425 --- /dev/null +++ b/tests/typecheck_function_unknown_return_expression_bad.claro @@ -0,0 +1,3 @@ +TEACH square amount RETURNS NUMBER + RETURN missing +END \ No newline at end of file diff --git a/tests/typecheck_function_unknown_return_expression_good.claro b/tests/typecheck_function_unknown_return_expression_good.claro new file mode 100644 index 0000000..73c213a --- /dev/null +++ b/tests/typecheck_function_unknown_return_expression_good.claro @@ -0,0 +1,4 @@ +TEACH square amount RETURNS NUMBER + CHECK TYPE amount IS NUMBER + RETURN amount +END \ No newline at end of file diff --git a/tools/validate_typecheck_diagnostics.py b/tools/validate_typecheck_diagnostics.py index caac69d..8b99d02 100644 --- a/tools/validate_typecheck_diagnostics.py +++ b/tools/validate_typecheck_diagnostics.py @@ -31,6 +31,9 @@ EXPECTED = { "tests/typecheck_function_return_expression_bad.claro": [ "tests/typecheck_function_return_expression_bad.claro:3: Type mismatch for return from label: expected TEXT, but this value looks like NUMBER.", ], + "tests/typecheck_function_unknown_return_expression_bad.claro": [ + "tests/typecheck_function_unknown_return_expression_bad.claro:2: Function square's return value could not be understood yet. Use a NUMBER expression after RETURN.", + ], "tests/typecheck_function_missing_return_bad.claro": [ "tests/typecheck_function_missing_return_bad.claro:3: Function square declares RETURNS NUMBER but has no RETURN statement. Add RETURN with a NUMBER value.", ], @@ -236,6 +239,7 @@ EXPECTED_OK = [ "tests/typecheck_function_compat_lowercase_return_good.claro", "tests/typecheck_function_lowercase_return_good.claro", "tests/typecheck_function_return_expression_good.claro", + "tests/typecheck_function_unknown_return_expression_good.claro", "tests/typecheck_function_branch_complete_good.claro", "tests/typecheck_function_nested_branch_complete_good.claro", "tests/typecheck_method_return_good.claro",