diff --git a/CHANGELOG.md b/CHANGELOG.md index c3eed1f..b6c8431 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +### Diagnose missing values after `AS ... TO` field annotations + +- `claro typecheck` now explains how to repair an explicitly typed object-field assignment that ends after `TO`, such as `SET player.score AS NUMBER TO`. +- Added focused negative coverage to the complete typecheck validation matrix. + ### Diagnose extra words after short typed field assignments - `claro typecheck` now rejects trailing words after the value in short explicit object-field assignments such as `SET player.score NUMBER 10 extra`. diff --git a/docs/ADVANCED_STATIC_TYPING.md b/docs/ADVANCED_STATIC_TYPING.md index 9d89ae6..a57d19f 100644 --- a/docs/ADVANCED_STATIC_TYPING.md +++ b/docs/ADVANCED_STATIC_TYPING.md @@ -832,6 +832,16 @@ with a repair hint instead of being silently ignored: SET player.score AS NUMBER TO 10 extra ``` +The separated form also needs a value after `TO`: + +```claro +SET player.score AS NUMBER TO +``` + +```text +SET player.score needs a value after type NUMBER and TO. Add one expression. +``` + ```text SET player.score has extra text after value 10. Keep only the field name, type, TO, and one expression. ``` diff --git a/docs/CURRENT_STATUS.md b/docs/CURRENT_STATUS.md index 623f437..139be0e 100644 --- a/docs/CURRENT_STATUS.md +++ b/docs/CURRENT_STATUS.md @@ -83,7 +83,7 @@ Ready now: - 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, dedicated positive and explicitly typed NUMBER/TEXT 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 unknown field with a repair hint in both method syntaxes, including compatibility `TAKES` / `LEARNED` methods; 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. - inline method-field annotations are checked against the class `HAS` declaration in modern and compatibility methods; conflicting known types and unknown annotation names produce field-specific repair guidance, with positive and negative focused fixtures for both syntaxes. -- direct object-field `AS ... TO` assignments reject extra words after the value with a repair hint, so `SET player.score AS NUMBER TO 10 extra` cannot silently ignore the trailing text; short explicitly typed assignments such as `SET player.score NUMBER 10 extra` receive the same repair-oriented check +- direct object-field `AS ... TO` assignments reject extra words after the value with a repair hint, so `SET player.score AS NUMBER TO 10 extra` cannot silently ignore the trailing text; they also explain when the value is missing after `TO`; short explicitly typed assignments such as `SET player.score NUMBER 10 extra` receive the same repair-oriented check - Direct object-field validation also has positive coverage for explicit `NUMBER`, `TEXT`, and `YESNO` annotations, such as `SET player.ready YESNO YES`, plus the compatibility-shaped `AS ... TO` form such as `SET player.score AS NUMBER TO 10`, while the shorter unannotated form remains supported; method-body field assignments have matching positive coverage for both modern and compatibility `TEACH` forms using `SET score AS NUMBER TO 10`, and those two `AS ... TO` method fixtures are now included in `claro validate`; conflicting annotations such as `SET player.score TEXT "oops"` explain the class-declared type and how to repair it, and unknown annotations such as `SET player.score AS BANANA TO 10` or `SET player.score BANANA 10` identify the invalid type and suggest the declared field type. Still needed: diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index a275cd1..a560e64 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -54,6 +54,7 @@ Keep declared return types honest: `claro typecheck` now reports friendly diagno 8d.3. Keep release validation aligned with method annotation coverage: the modern and compatibility `AS ... TO` method fixtures now run through `claro validate`, not only the focused Python diagnostic validator. 8d.4. Keep separated direct field assignments unambiguous: `SET player.score AS NUMBER TO 10 extra` now reports the trailing text and explains the one-expression form instead of accepting a partially parsed value. 8d.5. Keep short direct field assignments unambiguous: `SET player.score NUMBER 10 extra` now rejects trailing words with a repair hint, matching the separated `AS ... TO` form. +8d.6. Keep separated direct field assignments complete: `SET player.score AS NUMBER TO` now explains that one value expression is required after `TO`. 8e. Keep inline method-field annotation coverage aligned across syntax generations: compatibility `TAKES` / `LEARNED` methods now have matching positive and negative fixtures, so older lessons retain the same class-declared-type guidance. 8f. Keep inline method-field annotations learner-facing: an unknown annotation such as `BANANA` now names the field and method and suggests the `HAS` type instead of silently treating the annotation as an expression. 8g. Keep unknown inline method-field annotation diagnostics aligned across syntax generations: compatibility `TAKES` / `LEARNED` methods now have matching focused coverage for misspelled annotations. diff --git a/src/claro.c b/src/claro.c index 1a39726..c224054 100644 --- a/src/claro.c +++ b/src/claro.c @@ -647,7 +647,7 @@ else if(!strcmp(up,"IF")){ if(in_teach){ if(nested_blocks<64){ branch_returned[n 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; current_method[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){ char first_value[128]; if(return_value_extra_tokens(return_expr,first_value,sizeof(first_value))){ printf("%s:%d: %s %s has extra text after return value %s. Keep only one expression after RETURN.\n",path,line_no,strchr(current_func,'.')?"Method":"Function",current_func,first_value); } else { 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)){ char text_operator=(ci_eq(need,"NUMBER")&&ci_eq(got,"TEXT"))?text_operator_for_number_return(types,return_expr):0; const char *text_operand=text_operator?text_operand_for_operator(types,return_expr,text_operator):NULL; if(text_operand){ printf("%s:%d: Type mismatch for return from %s: %s needs NUMBER values, but %s looks like TEXT.\n",path,line_no,current_func,text_operator=='+'?"addition":(text_operator=='-'?"subtraction":(text_operator=='*'?"multiplication":"division")),text_operand); } else { 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; const char *as=find_word_ci(t+3,"AS"), *to=find_word_ci(t+3,"TO"); 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; if(as&&to&&as