///|
// Keep declaration nodes separate until scope inheritance has been applied.
// Properties carry their declaration's dependency; the symbol dependency and
// prompt visibility are the disjunction of all declaration-local alternatives.
fn merge_declarations(parsed : Schema) -> Schema raise ConfigError {
  let out = Schema::new(linux612=parsed.linux612)
  for node in parsed.defs {
    if parsed.defs.filter(fn(d) { d.name == node.name }).length() == 1 {
      for i, edge in node.selects {
        node.selects[i] = (edge.0, Conjunction(node.depends, edge.1))
      }
      for i, edge in node.implies {
        node.implies[i] = (edge.0, Conjunction(node.depends, edge.1))
      }
      out.defs.push(node)
      continue
    }
    let existing = out.defs.search_by(fn(d) { d.name == node.name })
    let d = match existing {
      Some(i) => out.defs[i]
      None => {
        let fresh = definition(node.name, None, node.location)
        fresh.depends = Constant(Logic(N))
        fresh.prompt_if = Constant(Logic(N))
        out.defs.push(fresh)
        fresh
      }
    }
    match node.kind {
      Some(kind) => set_kind(d, kind, linux612=parsed.linux612)
      None => ()
    }
    d.depends = Disjunction(d.depends, node.depends)
    if node.prompt != None {
      if d.prompt == None {
        d.prompt = node.prompt
      }
      d.prompt_if = Disjunction(
        d.prompt_if,
        Conjunction(node.depends, node.prompt_if),
      )
    }
    for (value, condition) in node.defaults {
      d.defaults.push((value, Conjunction(node.depends, condition)))
    }
    for (target, condition) in node.selects {
      d.selects.push((target, Conjunction(node.depends, condition)))
    }
    for (target, condition) in node.implies {
      d.implies.push((target, Conjunction(node.depends, condition)))
    }
    for (lo, hi, condition) in node.ranges {
      d.ranges.push((lo, hi, Conjunction(node.depends, condition)))
    }
    d.modules = d.modules || node.modules
    if d.help.is_empty() {
      d.help = node.help
    }
  }
  for node in parsed.defs {
    out.nodes.push(node)
  }
  for choice in parsed.choices {
    out.choices.push(choice)
  }
  out
}

///|
/// All declaration locations, in source traversal order. Builder symbols have
/// the single location "builder"; callers receive a fresh array.
pub fn Schema::source_locations(
  self : Schema,
  name : String,
) -> Array[String] raise ConfigError {
  ignore(self.index(name))
  let nodes = self.nodes.filter(fn(d) { d.name == name })
  if nodes.is_empty() {
    [self.defs[self.index(name)].location]
  } else {
    nodes.map(fn(d) { d.location })
  }
}