DEV Community

Mahiro Hirakawa
Mahiro Hirakawa

Posted on

My checker reported 11 unknown names. Declaring all 11 in one place would have passed and been wrong.

A semantic check over a codebase reported:

op_unknown=11
Enter fullscreen mode Exit fullscreen mode

Eleven identifiers appearing in code that the project's vocabulary does not declare. There is an obvious way to clear that: add eleven entries to the vocabulary file and watch the number go to zero.

That would have worked, in the sense that the check would have gone green.

The eleven were two different kinds of thing

Reading them instead of counting them:

kind count what they are where they are declared
instruments 6 things a module does: run, checks, tests, bound, face, vectors the module's own block, in the form its siblings use
symbols 5 things a layer defines: apply, deltas, corpus, resolve, kern a separate symbols registry, not the module block at all

The check counts both as "an identifier I do not recognise", because from where it stands that is all it can see. The two categories have different homes, different downstream readers, and different meanings.

Declaring a symbol in the instrument block would have satisfied the check. It would also have told every consumer of the instrument list that the layer performs an operation it does not perform. The list of things a module does is read by other tooling to decide what can be invoked, what needs a control, and what appears in a surface inventory. Five wrong entries there is worse than eleven honest unknowns.

A count of unrecognised things is not a task list. It is an unsorted pile, and sorting it is the work.

What the sorting cost

The six instruments went in by retiring the existing cells and standing the corrected ones, because the block they belong to is append-only. Twelve retired cells in a single row, to add six.

The five symbols went into the symbols registry, from a draft that had been proved separately, and regenerated in a scratch environment first:

OK_SEMANTIC  stale=0  op_unknown=0
Enter fullscreen mode Exit fullscreen mode

Neither half could have been done in the other's file. There was no version of this where one edit cleared the number.

Then the check stayed red anyway

This is the part that cost the most time, and it is not about vocabulary at all.

After both registries were correct, the live check still reported unknowns. The declarations were right and the check disagreed, which normally means the declarations are wrong.

They were not. The check does not read the declaration. It reads a generated page derived from the declaration.

declaration  ->  [ regeneration ]  ->  generated page  ->  the check reads this
Enter fullscreen mode Exit fullscreen mode

Nothing the check sees changes until the generated page is rebuilt, and rebuilding it is only meaningful from a committed state, because an artifact derived from a working tree cannot be reproduced by anyone else.

So the sequence is: declare, commit, regenerate, then look. Looking earlier produces a red that means "you have not regenerated yet" while wearing the costume of "your declaration is wrong".

Three things I would keep

Read the list before you clear it. op_unknown=11 invited one action. The right response was two actions in two files, and the single action would have passed.

Making a check pass and recording the truth are different goals that agree most of the time. When they disagree, the check is the thing that should lose. It is downstream of the truth, not the definition of it.

Know whether your checker reads your source or a copy of it. If it reads a derived artifact, a correct fix produces no change until the artifact is rebuilt, and the natural conclusion from "I fixed it and nothing moved" is that the fix was wrong. I have thrown away correct changes that way.

Top comments (0)