use brink_syntax_native::SyntaxNode;
use brink_syntax_native::ast::AstNode;
use rowan::TextRange;
use crate::hir::FileId;
use crate::provenance::{KindToken, NodeClass, Provenance, ProvenanceResolver};
#[must_use]
pub fn native_provenance(file: FileId, class: NodeClass, syntax: &SyntaxNode) -> Provenance {
Provenance {
file,
range: syntax.text_range(),
kind: KindToken {
class,
raw: syntax.kind() as u16,
},
}
}
pub struct NativeProvenanceResolver<'a> {
file: FileId,
root: &'a SyntaxNode,
}
impl<'a> NativeProvenanceResolver<'a> {
#[must_use]
pub fn new(file: FileId, root: &'a SyntaxNode) -> Self {
Self { file, root }
}
#[must_use]
pub fn resolve_ast<N: AstNode>(&self, provenance: Provenance) -> Option<N> {
self.resolve(provenance).and_then(N::cast)
}
}
impl ProvenanceResolver for NativeProvenanceResolver<'_> {
type Node = SyntaxNode;
fn resolve(&self, provenance: Provenance) -> Option<SyntaxNode> {
if provenance.file != self.file {
return None;
}
resolve_by_raw_kind(self.root, provenance.kind.raw, provenance.range)
}
}
fn resolve_by_raw_kind(root: &SyntaxNode, raw: u16, range: TextRange) -> Option<SyntaxNode> {
if !root.text_range().contains_range(range) {
return None;
}
let mut node = root.covering_element(range);
loop {
match &node {
rowan::NodeOrToken::Node(n) => {
if n.text_range() == range && n.kind() as u16 == raw {
return Some(n.clone());
}
if n.text_range().start() < range.start() {
return None;
}
let parent = n.parent()?;
node = rowan::NodeOrToken::Node(parent);
}
rowan::NodeOrToken::Token(t) => {
let parent = t.parent()?;
node = rowan::NodeOrToken::Node(parent);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use brink_syntax_native::ast;
#[test]
fn resolver_round_trips_a_native_node() {
let parse = brink_syntax_native::parse("flow greet() {\n Hi!\n}\n");
let root = parse.syntax();
let file = FileId(0);
let source_file = ast::SourceFile::cast(root.clone()).expect("SOURCE_FILE root");
let flow = source_file.flows().next().expect("one flow");
let prov = native_provenance(file, NodeClass::Knot, flow.syntax());
let resolver = NativeProvenanceResolver::new(file, &root);
let live_node = resolver.resolve(prov).expect("resolves back");
assert_eq!(live_node.text_range(), flow.syntax().text_range());
let typed: ast::FlowDecl = resolver.resolve_ast(prov).expect("resolves to FlowDecl");
assert_eq!(typed.name_token().unwrap().text(), "greet");
}
#[test]
fn foreign_file_id_never_resolves() {
let parse = brink_syntax_native::parse("flow greet() {}\n");
let root = parse.syntax();
let source_file = ast::SourceFile::cast(root.clone()).expect("SOURCE_FILE root");
let flow = source_file.flows().next().expect("one flow");
let prov = native_provenance(FileId(0), NodeClass::Knot, flow.syntax());
let resolver = NativeProvenanceResolver::new(FileId(1), &root);
assert!(resolver.resolve(prov).is_none());
}
#[test]
fn synthetic_provenance_never_resolves() {
let parse = brink_syntax_native::parse("flow greet() {}\n");
let root = parse.syntax();
let file = FileId(0);
let synthetic = Provenance::synthetic(NodeClass::Knot, root.text_range());
let resolver = NativeProvenanceResolver::new(file, &root);
assert!(resolver.resolve(synthetic).is_none());
}
}