Exercise 3: Reproducing and Fixing the Naive Inheritance Bug — Possible Solution ==================================================================== THE TEST ------------------------------ grandparent (id="g", color: red) -> parent (no rule) -> child (no rule) compute_style_naive(child, sheet)['color'] # buggy version compute_style_tree(grandparent, sheet); child.computed_style['color'] # fixed version RESULT ------------------------------ compute_style_naive(child, sheet)['color'] -> 'black' (WRONG) child.computed_style['color'] (fixed) -> 'red' (correct) REPRODUCING THE BUG ------------------------------ compute_style_naive looks ONLY at the immediate parent's own direct cascade() result: parent_own = cascade(element.parent, stylesheet) for prop in INHERITED_PROPERTIES: if prop in parent_own: result[prop] = parent_own[prop] For child, element.parent is parent. cascade(parent, stylesheet) returns {} -- no rule in the stylesheet has a selector that matches parent directly (the only rule is '#g', which matches grandparent, not parent). Since parent_own has no 'color' key at all, the `if prop in parent_own` check fails, nothing is copied into result, and color stays at the INITIAL_VALUES default, 'black'. The function never looks past the immediate parent to find that grandparent is where the real color value actually lives, and it never accounts for the fact that parent itself should ALSO have inherited red from grandparent, even though parent's own cascade() has nothing to say about color. THE FIX ------------------------------ compute_style_tree replaces the single non-recursive parent lookup with a top-down walk, where each node's parent_computed argument is its own parent's FULLY RESOLVED computed_style -- not that parent's raw cascade() result: def compute_style_tree(root, stylesheet, parent_computed=None): if root.kind != 'element': return root.computed_style = compute_style(root, stylesheet, parent_computed) for child in root.children: compute_style_tree(child, stylesheet, root.computed_style) Walking from grandparent downward: grandparent's own cascade() DOES have color='red', so grandparent.computed_style ends up with color='red'. When the walk reaches parent, it's handed grandparent.computed_style (which already has color='red') as its own parent_computed -- so parent inherits red, even though parent's OWN cascade() is empty, because inheritance now happens through the fully resolved computed value, not the raw per-element rule match. Then when the walk reaches child, it's handed parent.computed_style, which by this point ALREADY contains the inherited red -- so child inherits it too, transitively, without ever having to look more than one level up itself. WHY THIS WORKS AS AN ANSWER ------------------------------ The bug wasn't in the concept of inheritance -- it was in WHERE compute_style_naive looked for a value to inherit. Looking at a parent's raw, direct cascade() result only captures rules that match that parent specifically; it has no way to see a value the parent itself only has because IT inherited it from somewhere further up. The fix works by never re-deriving an ancestor's style from scratch at each level -- instead, the tree walk computes every node's style exactly once, in top-down order, and always hands each child the parent's own already-complete answer. Inheritance becomes correct at any depth for free, because by construction, a parent's computed_style can never be incomplete by the time a child asks to inherit from it.