Fix silent shape errors#272
Conversation
|
This sort of matches what we do to report pipeline compilation errors (although that's in an imperative loop and not a When), and it should work, but isn't really the solution I was hoping for -- if possible, I think it would be better to fix this problem in general by having any error in a When block bubble up to the statement producer, although I'm not sure how hard that is (we would probably Say an error statement per level of the 'statement trace', walking up the match/statement graph, instead of just Say for the leaf level? again, not sure if that works in practice, but something like that) (and then we wouldn't need to insert these repetitive try-catch blocks at every point in the standard library where we want to report errors to caller) |
|
Gotcha, yeah, that makes sense. I went with this approach because it was the most conservative but I think it’s pretty tractable to add something in prelude.tcl that can walk the graph and issue that |
This commit resolves an issue where errors from evaluated When
blocks were either lost or incorrectly attributed when multiple
parent statements contributed to a match. It propagates errors
up through the bipartite graph to all responsible ancestor
statements, while properly handling stack traces and Claim
statements for error rendering.
- db.c / db.h:
- Update Statement and Match creation to accept and store an
array of all parent statements rather than a single parent.
- Add dbGetCausalTrace to walk the causal graph (BFS) and
return a list of all ancestor program files.
- folk.c:
- Move error propagation out of Tcl and into C's runWhenBlock.
- Traverse dbGetCausalTrace on JIM_ERR and dispatch
SayWithSource to report errors to all responsible ancestors.
- Properly format -errorinfo tuples and safely strdup the
error message to prevent JimTcl memory corruption.
- prelude.tcl:
- Remove single-parent error bubbling logic from evaluateBlock.
- Let -code error pass the error to runWhenBlock natively.
- builtin-programs/errors.folk:
- Update error and warning matchers to also match Claim
statement outputs so bubbled errors render on the table.
|
Alright, with
This involved making a few deeper (but relatively small!) changes:
|
| Wish $p is titled $err | ||
| } | ||
|
|
||
| When /someone/ claims /p/ has error /err/ with info /info/ { |
There was a problem hiding this comment.
Do we need these new Whens in this file? Do they fix anything? These cases should already be handled by the non-claims When
| } | ||
|
|
||
| When /someone/ wishes /p/ draws a /shape/ with /...options/ &\ | ||
| When /wisher/ wishes /p/ draws a /shape/ with /...options/ &\ |
There was a problem hiding this comment.
Can you revert all the changes to this file? (In general, can you read the diff and remove cruft like this before asking for review?)
There was a problem hiding this comment.
@cwervo mentioned that in the PR request, "We change someone to wisher so we don't lose the context of the wisher program," but I'm not entirely sure why /someone/ would be losing context?
|
Will test and read the new code soon, thanks! |
| // The program that caused the chain of matches leading to this statement. | ||
| char causalityFileName[100]; |
There was a problem hiding this comment.
I'm really struggling to understand what "causality" means in this context. This is the comment that should go more in detail as to what causality means, or it should at least refer to a part of the code where causality is detailed. This would be the place to put "why" comment, not a "what" comment.
There was a problem hiding this comment.
I guess my main question is why this is different than sourceFileName, or getting the filename by following to the parent?
| int workerThreadIndex; | ||
|
|
||
| int nParentStatements; | ||
| StatementRef* parentStatements; |
There was a problem hiding this comment.
I'm not a massive fan of bidirectional pointers, but I think in this case it makes sense, because traversal upwards would be tricky otherwise.
| trace[0] = '\0'; | ||
|
|
||
| // Simple BFS queue | ||
| MatchRef queue[1024]; |
There was a problem hiding this comment.
Not a massive fan of hardcoding 1024 here, since it's used in a couple different places below where it could desynchronize pretty quickly. Also could you expand the BFS acronym? I'm assuming you mean breadth first search, but it took me a bit to figure that out.
| } | ||
| } | ||
|
|
||
| // Returns a comma-separated list of unique files in the causal trace. |
There was a problem hiding this comment.
Is this topologically sorted? Might be worth mentioning in the comment, since otherwise someone may be unsure if it could be used for a stack trace (nice idea btw of using unique file names).
| // add to trace if unique | ||
| if (fname && strlen(fname) > 0 && strcmp(fname, "(null)") != 0) { | ||
| // Check if already in trace | ||
| if (!strstr(trace, fname)) { |
There was a problem hiding this comment.
This becomes O(n^2) which is probably fine for now, but might be worth adding a TODO. Same with the strcat, since it uses a linear scan on each append.
| MatchRef parent, | ||
| StatementRef* outReusedStatementRef); | ||
|
|
||
| // Returns a comma-separated list of unique files in the causal trace. |
There was a problem hiding this comment.
Again, there's the term "causal trace", but I never really feel like it's been defined anywhere.
| Jim_DictAddElement(interp, errorInfoObj, | ||
| Jim_NewStringObj(interp, "-errorinfo", -1), | ||
| errorInfoStr); | ||
| Jim_Obj *cmd[] = { |
There was a problem hiding this comment.
Can't you call the function here directly? iirc you don't need to go through the interpreter for this.
| sourceFileName, sourceLineNumber); | ||
| StatementRef ref = statementNew(db, clause, keepMs, atomicallyVersion, | ||
| sourceFileName, sourceLineNumber, | ||
| parentMatch ? parentMatch->causalityFileName : sourceFileName, |
There was a problem hiding this comment.
So is "causality" essentially the first Wish or Claim? Won't that mean that "causality" will always just be whatever the very first claim is, i.e. the claim from the interpreter at the very beginning?
| pthread_mutex_unlock(&parentStatements[i]->destructorSetMutex); | ||
|
|
||
| // Inherit causality from the last parent (usually the triggering statement). | ||
| if (i == nParents - 1) { |
There was a problem hiding this comment.
Why is this in the for loop at all? Plus why are the multiple parent statements being tracked if you flatten out causality? I think this would work with a single parentStatementRef, instead of all the overhead for moving around the statements list. Remember, currently there's only two possible length of parentStatements: a root When, which has only one parent (the When), and when the When unifies with a statement, so the parent statements are the When and the statement it unified with.
| Jim_MakeErrorMessage(interp); | ||
| const char *errorMessage = Jim_GetString(Jim_GetResult(interp), NULL); | ||
| const char *errorMessageRaw = Jim_GetString(Jim_GetResult(interp), NULL); | ||
| char *errorMessage = strdup(errorMessageRaw ? errorMessageRaw : "Unknown error"); |
There was a problem hiding this comment.
Jim_GetString guarantees to return a string, so this is unnecessary defensive programming.
|
|
||
| const char* fileName = statementSourceFileName(stmt); | ||
| const char* fileName = statementCausalityFileName(stmt); | ||
| int lineNumber = statementSourceLineNumber(stmt); |
There was a problem hiding this comment.
Don't you need to track the causality line number?
| Jim_Obj *cmd[] = { | ||
| Jim_NewStringObj(interp, "SayWithSource", -1), | ||
| Jim_NewStringObj(interp, tok, -1), /* sourceFileName */ | ||
| Jim_NewIntObj(interp, 1), /* sourceLineNumber */ |
There was a problem hiding this comment.
Line number is erased here too.
|
Overall I think this is a really cool idea, and the implementation has some unique solutions to the problem. However, reading through this, I felt like causality is not well defined. I think I've pieced together what the code does? Essentially, causality tracks the line information of the root In general, I feel like causality as conceived by this PR still has some architectural issues:
I'm really excited to see where this goes! |

After merging #261 we introduced an issue with
draw/shapes.folkerrors, for example:this should produce an error, but it gets swallowed by the
shapes.folkfile.The fix
someonetowisherso we don't lose the context of the wisher programdraw/shapes.folkthat could throw an error intry/on errorand bubble the error to the wisher.