code: func ruleDeps(rule *ast.Rule) (resolved []ast.Ref) {
  	vars, others := extractEq(rule.Body)
  	joined := joinVarRefs(vars)
  
  	headVars := rule.Head.Vars()
  	headRefs, others := resolveOthers(others, headVars, joined)
  
  	resolveRef := func(r ast.Ref) bool {
  		resolved = append(resolved, expandRef(r, joined)...)
  		return false
  	}
  
  	varVisitor := ast.NewVarVisitor().WithParams(ast.VarVisitorParams{SkipRefHead: true})
  	// Clean up whatever refs are remaining among the other expressions.
  	for _, expr := range others {
  		ast.WalkRefs(expr, resolveRef)
  		ast.Walk(varVisitor, expr)
  	}
  
  	// If a reference ending in a header variable is a prefix of an already
  	// resolved reference, skip it and simply walk the nodes below it.
  	visitor := skipVisitor{resolveRef}
  	for _, r := range headRefs {
  		if !containsPrefix(resolved, r) {
  			resolved = append(resolved, r.Copy())
  		}
  		ast.Walk(visitor, r)
  	}
  
  	usedVars := varVisitor.Vars()
  
  	// Vars included in refs must be counted as used.
  	ast.WalkRefs(rule.Body, func(r ast.Ref) bool {
  		for i := 1; i < len(r); i++ {
  			if v, ok := r[i].Value.(ast.Var); ok {
  				usedVars.Add(v)
  			}
  		}
  		return false
  	})
  
  	resolveRemainingVars(joined, visitor, usedVars, headVars)
  	return resolved
  }
docs: // FIXME(tsandall): this logic should be revisited as it seems overly
  // complicated. It should be possible to compute all dependencies in two
  // passes:
  //
  // 1) perform syntactic unification on vars
  // 2) gather all refs rooted at data after plugging the head with substitution
  //    from (1)