Katkat

Hunt Three Planted Bugs in a Small Template Compiler Called Wick

StripeSoftware Engineer · Technical Screen

Last updated

Trace three reported failures in a small Python template compiler — a dropped ternary else-branch, a directory mistaken for a template file, and a comment-only loop body that compiles to invalid source — to their root causes and fix each with a regression test.

Wick is a small template engine, similar in spirit to Mako or Jinja. It parses .wick templates into text, ${expr} expressions, and % if / % for control blocks, then compiles the template into real Python source code that it later executes to render output. Three bugs have been planted in Wick's compiler. Each is isolated below as a unit the compiler relies on. Find each bug's root cause, fix it with the smallest correct change, and add a regression test.

Part 1 — A ternary default value silently loses its "else"

Wick lets a template macro declare a default argument using any Python expression, including a ternary:

<%def name="greet(user, title=('Mx' if user.get('formal') else 'Hey'))">

To emit this macro as a real Python def, the compiler re-serializes the parsed default-value expression back into source text using its own small expression printer (it predates ast.unparse). Reported symptom: no matter what user.get('formal') evaluates to at render time, title always comes out 'Mx'.

Part 2 — A directory shadows a real template file

Wick resolves a template name by searching a list of directories in order and returning the first path that "exists." Reported symptom: when an earlier search directory happens to contain a sub-directory with the exact same name as a requested template (no file extension match needed — just the name), Wick returns that directory instead of continuing on to find the real template file in a later directory, and rendering then fails trying to read a directory as a file.

Part 3 — A loop whose body is only a comment fails to compile

A % for block that has at least one line, but every line is a ## comment, should render as a no-op loop. Reported symptom: compiling such a template raises IndentationError, not a rendering error.

What to deliver

For each part: the root cause in one or two sentences, a minimal fix, and a test that fails before the fix and passes after it.

Hints

Hint 1

For Part 1, print `ast.dump()` of the parsed default expression and compare it, node type by node type, with what the printer function actually handles.

Hint 2

For Part 2, the bug is a missing distinction between 'a path exists' and 'a path is a readable file' — `os`'s standard library draws that line in one specific function.

Hint 3

For Part 3, think about what Python source text a block with an empty body compiles down to, and what every valid Python suite needs at minimum.

Hint 4

Fix each bug independently and re-run all three regression tests together at the end to make sure one fix didn't mask another.

Follow-up questions

  • How would you extend the expression printer to handle nested ternaries or boolean `and`/`or` chains without more special cases?
  • Should template lookup fail loudly the first time it encounters a name collision between a directory and a file, instead of silently preferring the later match?
  • How would you add a compiler-level warning for a block whose body is only comments, instead of silently rendering a no-op?
  • If you had to migrate this hand-rolled printer onto `ast.unparse`, what behavior differences would you need to check for first?