use crate::ast;
use crate::cst::Parse;
use crate::syntax::{SyntaxKind, SyntaxNode};
use crate::{
position_at_source, Expr, Node, ParseDocumentError, PatternBinding, RefBase, TokenKey,
TokenRange,
};
thread_local! {
static RECOVERING: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
}
pub(crate) fn is_recovering() -> bool {
RECOVERING.with(|c| c.get())
}
pub(crate) struct RecoveringScope {
prev: bool,
}
impl RecoveringScope {
pub(crate) fn enter() -> Self {
let prev = RECOVERING.with(|c| c.replace(true));
RecoveringScope { prev }
}
}
impl Drop for RecoveringScope {
fn drop(&mut self) {
RECOVERING.with(|c| c.set(self.prev));
}
}
pub fn range_from_offsets(source: &str, start: usize, end: usize) -> TokenRange {
TokenRange {
start: position_at_source(source, start),
end: position_at_source(source, end),
}
}
fn parse_number_text(text: &str) -> Option<crate::Expr> {
use ordered_float::OrderedFloat;
let bytes = text.as_bytes();
let (sign, rest) = match bytes.first() {
Some(b'+') => (1i64, &text[1..]),
Some(b'-') => (-1i64, &text[1..]),
_ => (1i64, text),
};
if let Some(hex) = rest.strip_prefix("0x") {
if hex.is_empty() || !hex.bytes().all(|b| b.is_ascii_hexdigit()) {
return None;
}
let v: u64 = u64::from_str_radix(hex, 16).ok()?;
let signed = if sign >= 0 {
i64::try_from(v).ok()?
} else if v > (i64::MAX as u64) + 1 {
return None;
} else if v == (i64::MAX as u64) + 1 {
i64::MIN
} else {
-(v as i64)
};
return Some(crate::Expr::Int(signed));
}
if let Some(oct) = rest.strip_prefix("0o") {
if oct.is_empty() {
return None;
}
let v: i64 = i64::from_str_radix(oct, 8).ok()?;
return Some(crate::Expr::Int(v.checked_mul(sign)?));
}
if let Some(bin) = rest.strip_prefix("0b") {
if bin.is_empty() {
return None;
}
let v: i64 = i64::from_str_radix(bin, 2).ok()?;
return Some(crate::Expr::Int(v.checked_mul(sign)?));
}
if rest == "Infinity" {
return Some(crate::Expr::Float(OrderedFloat(if sign == 1 {
f64::INFINITY
} else {
f64::NEG_INFINITY
})));
}
if rest == "NaN" {
return Some(crate::Expr::Float(OrderedFloat(f64::NAN)));
}
if rest.contains('.') || rest.contains('e') || rest.contains('E') {
let f: f64 = rest.parse().ok()?;
return Some(crate::Expr::Float(OrderedFloat(f * sign as f64)));
}
let i: i64 = rest.parse().ok()?;
Some(crate::Expr::Int(i.checked_mul(sign)?))
}
fn parse_string_text(text: &str) -> Option<String> {
let bytes = text.as_bytes();
if bytes.first().copied() == Some(b'r') {
return parse_raw_string_text(&text[1..]);
}
parse_normal_string_text(text)
}
fn parse_normal_string_text(text: &str) -> Option<String> {
let inner = text.strip_prefix('"')?.strip_suffix('"')?;
let mut out = String::with_capacity(inner.len());
let mut chars = inner.chars().peekable();
while let Some(c) = chars.next() {
if c != '\\' {
out.push(c);
continue;
}
let escape = chars.next()?;
match escape {
'n' => out.push('\n'),
'r' => out.push('\r'),
't' => out.push('\t'),
'b' => out.push('\u{08}'),
'f' => out.push('\u{0C}'),
'\\' => out.push('\\'),
'/' => out.push('/'),
'"' => out.push('"'),
'u' => {
let cp = if chars.peek().copied() == Some('{') {
chars.next(); let mut hex = String::new();
loop {
let h = chars.next()?;
if h == '}' {
break;
}
if !h.is_ascii_hexdigit() {
return None;
}
hex.push(h);
}
if hex.is_empty() || hex.len() > 6 {
return None;
}
u32::from_str_radix(&hex, 16).ok()?
} else {
let mut hex = String::new();
for _ in 0..4 {
let h = chars.next()?;
if !h.is_ascii_hexdigit() {
return None;
}
hex.push(h);
}
u32::from_str_radix(&hex, 16).ok()?
};
out.push(std::char::from_u32(cp)?);
}
w if w.is_whitespace() => {
while let Some(&peek) = chars.peek() {
if peek.is_whitespace() {
chars.next();
} else {
break;
}
}
}
_ => return None,
}
}
Some(out)
}
fn parse_raw_string_text(after_r: &str) -> Option<String> {
let mut hash_count = 0usize;
let bytes = after_r.as_bytes();
while bytes.get(hash_count).copied() == Some(b'#') {
hash_count += 1;
}
let rest = &after_r[hash_count..];
let inner = rest.strip_prefix('"')?;
let mut closing = String::from("\"");
for _ in 0..hash_count {
closing.push('#');
}
let content = inner.strip_suffix(closing.as_str())?;
Some(content.to_string())
}
#[allow(dead_code)]
fn lower_atom_via_legacy(node: &SyntaxNode, source: &str) -> Option<Node> {
match node.kind() {
SyntaxKind::VARIABLE_EXPR => lower_variable_expr_v2(node, source),
SyntaxKind::REFERENCE_EXPR => lower_reference_expr_v2(node, source),
SyntaxKind::WILDCARD => {
let r = node.text_range();
let start: usize = r.start().into();
let end: usize = r.end().into();
let slice = source.get(start..end)?;
if slice == "_" || slice == "*" {
Some(Node::new(
Expr::Wildcard,
range_from_offsets(source, start, end),
))
} else {
None
}
}
SyntaxKind::LITERAL => lower_literal_v2(node, source),
_ => None,
}
}
fn lower_literal_v2(node: &SyntaxNode, source: &str) -> Option<Node> {
let token = node
.children_with_tokens()
.filter_map(|el| el.into_token())
.find(|t| {
matches!(
t.kind(),
SyntaxKind::IDENT | SyntaxKind::NUMBER | SyntaxKind::STRING
)
})?;
let tr = token.text_range();
let start: usize = tr.start().into();
let end: usize = tr.end().into();
let range = range_from_offsets(source, start, end);
match token.kind() {
SyntaxKind::IDENT => match token.text() {
"null" => Some(Node::new(Expr::Missing, range)),
"true" => Some(Node::new(Expr::Bool(true), range)),
"false" => Some(Node::new(Expr::Bool(false), range)),
"Infinity" => Some(Node::new(
Expr::Float(ordered_float::OrderedFloat(f64::INFINITY)),
range,
)),
"NaN" => Some(Node::new(
Expr::Float(ordered_float::OrderedFloat(f64::NAN)),
range,
)),
_ => None,
},
SyntaxKind::NUMBER => {
let slice = source.get(start..end)?;
let expr = parse_number_text(slice)?;
Some(Node::new(expr, range))
}
SyntaxKind::STRING => {
let slice = source.get(start..end)?;
let s = parse_string_text(slice)?;
Some(Node::new(Expr::String(s), range))
}
_ => None,
}
}
fn lower_variable_expr_v2(node: &SyntaxNode, source: &str) -> Option<Node> {
let r = node.text_range();
let start: usize = r.start().into();
let end: usize = r.end().into();
let path = walk_path_tokens(node, source, false)?;
if path.len() == 1 {
if let TokenKey::String(name, name_range, false) = &path[0] {
if matches!(
name.as_str(),
"Int" | "String" | "Bool" | "Any" | "List" | "Dict"
) {
let t = crate::TypeNode {
path: vec![name.clone()],
generics: Vec::new(),
is_optional: false,
range: *name_range,
variant_fields: None,
doc_comment: None,
};
return Some(Node::new(
Expr::Type(t),
range_from_offsets(source, start, end),
));
}
}
}
Some(Node::new(
Expr::Variable(path),
range_from_offsets(source, start, end),
))
}
fn lower_reference_expr_v2(node: &SyntaxNode, source: &str) -> Option<Node> {
let r = node.text_range();
let start: usize = r.start().into();
let end: usize = r.end().into();
let base_text = node
.children_with_tokens()
.filter_map(|el| el.into_token())
.find(|t| t.kind() == SyntaxKind::IDENT)
.map(|t| t.text().to_string())?;
let base = match base_text.as_str() {
"root" => RefBase::Root,
"sibling" => RefBase::Sibling,
"uncle" => RefBase::Uncle,
"prev" => RefBase::Prev,
"next" => RefBase::Next,
"index" => RefBase::Index,
"this" => RefBase::This,
_ => return None,
};
let path = walk_path_tokens(node, source, true)?;
Some(Node::new(
Expr::Reference { base, path },
range_from_offsets(source, start, end),
))
}
fn walk_path_tokens(node: &SyntaxNode, source: &str, is_reference: bool) -> Option<Vec<TokenKey>> {
let mut path: Vec<TokenKey> = Vec::new();
let mut head_done = false;
let mut pending_optional = false;
let mut expect_segment_after_dot = false;
for el in node.children_with_tokens() {
match el {
rowan::NodeOrToken::Token(t) => match t.kind() {
SyntaxKind::WHITESPACE | SyntaxKind::LINE_COMMENT | SyntaxKind::BLOCK_COMMENT => {
continue
}
SyntaxKind::AMP => continue,
SyntaxKind::IDENT => {
if !head_done {
head_done = true;
if is_reference {
continue;
}
let tr = t.text_range();
let s: usize = tr.start().into();
let e: usize = tr.end().into();
path.push(TokenKey::String(
t.text().to_string(),
range_from_offsets(source, s, e),
false,
));
continue;
}
if expect_segment_after_dot {
let tr = t.text_range();
let s: usize = tr.start().into();
let e: usize = tr.end().into();
path.push(TokenKey::String(
t.text().to_string(),
range_from_offsets(source, s, e),
pending_optional,
));
pending_optional = false;
expect_segment_after_dot = false;
continue;
}
return None;
}
SyntaxKind::NUMBER => {
if expect_segment_after_dot {
let idx: usize = t.text().parse().ok()?;
path.push(TokenKey::Index(idx, pending_optional));
pending_optional = false;
expect_segment_after_dot = false;
continue;
}
return None;
}
SyntaxKind::DOT => {
expect_segment_after_dot = true;
continue;
}
SyntaxKind::QUESTION => {
pending_optional = true;
continue;
}
SyntaxKind::L_BRACK | SyntaxKind::R_BRACK => continue,
_ => continue,
},
rowan::NodeOrToken::Node(n) => {
if !head_done && !is_reference && n.kind() == SyntaxKind::VARIABLE_EXPR {
let inner_path = walk_path_tokens(&n, source, false)?;
path.extend(inner_path);
head_done = true;
continue;
}
if let Some(inner) = ast::Expr::cast(n) {
let inner_node = lower_expr_v2(&inner, source)?;
path.push(TokenKey::Dynamic(inner_node, pending_optional));
pending_optional = false;
}
}
}
}
Some(path)
}
#[allow(dead_code)]
fn trim_leading_trivia(slice: &str) -> usize {
let mut bytes = slice.as_bytes();
let mut offset = 0usize;
loop {
while let Some(&b) = bytes.first() {
if b == b' ' || b == b'\t' || b == b'\n' || b == b'\r' {
bytes = &bytes[1..];
offset += 1;
} else {
break;
}
}
if bytes.starts_with(b"//") {
let mut i = 2;
while i < bytes.len() && bytes[i] != b'\n' {
i += 1;
}
offset += i;
bytes = &bytes[i..];
continue;
}
if bytes.starts_with(b"/*") {
let mut i = 2;
while i + 1 < bytes.len() && !(bytes[i] == b'*' && bytes[i + 1] == b'/') {
i += 1;
}
if i + 1 < bytes.len() {
i += 2;
}
offset += i;
bytes = &bytes[i..];
continue;
}
break;
}
offset
}
#[allow(dead_code)]
fn lower_directive_v2(dir: &ast::Directive, source: &str) -> Option<crate::Directive> {
let node = dir.syntax();
let r = node.text_range();
let raw_start: usize = r.start().into();
let end: usize = r.end().into();
let raw_slice = source.get(raw_start..end)?;
let trim = trim_leading_trivia(raw_slice);
let start = raw_start + trim;
let name = dir.name()?;
let shape = crate::directive::directive_shape(&name)?;
let body = match shape {
crate::DirectiveShape::Bare => crate::DirectiveBody::Bare,
crate::DirectiveShape::Value => lower_directive_value_body(node, source)?,
crate::DirectiveShape::NameBody => lower_directive_name_body(node, source)?,
crate::DirectiveShape::Enum => lower_directive_enum_body(node, source)?,
crate::DirectiveShape::Import => lower_directive_import_body(node, source)?,
crate::DirectiveShape::Main => lower_directive_main_body(node, source)?,
};
let end_trimmed = directive_end_offset(node, &name, shape, end, source);
Some(crate::Directive {
name,
body,
range: range_from_offsets(source, start, end_trimmed),
})
}
fn directive_end_offset(
node: &SyntaxNode,
name: &str,
shape: crate::DirectiveShape,
cst_end: usize,
source: &str,
) -> usize {
let mut last_significant: Option<usize> = None;
for el in node.children_with_tokens() {
let kind = match &el {
rowan::NodeOrToken::Token(t) => t.kind(),
rowan::NodeOrToken::Node(n) => n.kind(),
};
let range = match &el {
rowan::NodeOrToken::Token(t) => t.text_range(),
rowan::NodeOrToken::Node(n) => n.text_range(),
};
if matches!(
kind,
SyntaxKind::WHITESPACE | SyntaxKind::LINE_COMMENT | SyntaxKind::BLOCK_COMMENT
) {
continue;
}
let end: usize = range.end().into();
if matches!(shape, crate::DirectiveShape::Bare)
&& kind == SyntaxKind::IDENT
&& last_significant.is_none()
{
last_significant = Some(end);
continue;
}
last_significant = Some(end);
}
let _ = (name, cst_end, source); last_significant.unwrap_or(cst_end)
}
fn lower_directive_value_body(node: &SyntaxNode, source: &str) -> Option<crate::DirectiveBody> {
let body_expr = node.children().find_map(ast::Expr::cast)?;
let body_node = lower_expr_v2(&body_expr, source)?;
Some(crate::DirectiveBody::Value(Box::new(body_node)))
}
fn lower_directive_import_body(node: &SyntaxNode, source: &str) -> Option<crate::DirectiveBody> {
let mut after_hash_name = false; let mut spec: Option<crate::DirectiveImportSpec> = None;
let mut path_string: Option<(String, TokenRange)> = None;
let mut destructure_open = false;
let mut destructure_entries: Vec<(String, Option<String>)> = Vec::new();
let mut pending_name: Option<String> = None;
let mut expect_as_alias = false;
let mut integrity_algo: Option<(String, usize)> = None; let mut integrity_saw_colon = false;
let mut integrity: Option<crate::IntegrityHash> = None;
for el in node.children_with_tokens() {
match el {
rowan::NodeOrToken::Token(t) => {
match t.kind() {
SyntaxKind::WHITESPACE
| SyntaxKind::LINE_COMMENT
| SyntaxKind::BLOCK_COMMENT
| SyntaxKind::HASH => continue,
SyntaxKind::IDENT => {
let text = t.text();
if !after_hash_name {
if text == "import" {
after_hash_name = true;
}
continue;
}
if destructure_open {
if expect_as_alias {
if let Some(prev) = pending_name.take() {
destructure_entries.push((prev, Some(text.to_string())));
}
expect_as_alias = false;
continue;
}
if text == "as" {
expect_as_alias = true;
continue;
}
if let Some(prev) = pending_name.take() {
destructure_entries.push((prev, None));
}
pending_name = Some(text.to_string());
continue;
}
if text == "from" {
continue;
}
if path_string.is_some() && integrity_algo.is_none() {
let start: usize = t.text_range().start().into();
integrity_algo = Some((text.to_string(), start));
continue;
}
if spec.is_none() {
spec = Some(crate::DirectiveImportSpec::Alias(text.to_string()));
}
}
SyntaxKind::STAR => {
if spec.is_none() {
spec = Some(crate::DirectiveImportSpec::Spread);
}
}
SyntaxKind::L_BRACE => {
destructure_open = true;
}
SyntaxKind::R_BRACE => {
if let Some(prev) = pending_name.take() {
destructure_entries.push((prev, None));
}
if destructure_open {
spec = Some(crate::DirectiveImportSpec::Destructure(
destructure_entries.clone(),
));
destructure_open = false;
}
}
SyntaxKind::COMMA => {
if destructure_open {
if let Some(prev) = pending_name.take() {
destructure_entries.push((prev, None));
}
}
}
SyntaxKind::COLON => {
if integrity_algo.is_some() && !integrity_saw_colon {
integrity_saw_colon = true;
}
}
SyntaxKind::STRING => {
let tr = t.text_range();
let s: usize = tr.start().into();
let e: usize = tr.end().into();
let raw = source.get(s..e)?;
let decoded = parse_string_text(raw)?;
if path_string.is_none() {
path_string = Some((decoded, range_from_offsets(source, s, e)));
} else if integrity_algo.is_some() && integrity_saw_colon {
let (algo_text, algo_start) = integrity_algo.take().unwrap();
let algo = crate::HashAlgorithm::from_ident(&algo_text);
integrity = Some(crate::IntegrityHash {
algorithm: algo,
algorithm_text: algo_text,
hex: decoded,
range: range_from_offsets(source, algo_start, e),
});
integrity_saw_colon = false;
}
}
_ => continue,
}
}
rowan::NodeOrToken::Node(_) => continue,
}
}
let spec = spec?;
let (path, path_range) = path_string?;
Some(crate::DirectiveBody::Import {
spec,
path,
path_range,
integrity,
})
}
fn lower_directive_main_body(node: &SyntaxNode, source: &str) -> Option<crate::DirectiveBody> {
let mut params: Vec<crate::DirectiveMainParam> = Vec::new();
let mut return_type: Option<crate::TypeNode> = None;
let mut saw_arrow = false;
for child in node.children_with_tokens() {
match child {
rowan::NodeOrToken::Token(t) => {
if t.kind() == SyntaxKind::THIN_ARROW {
saw_arrow = true;
}
}
rowan::NodeOrToken::Node(n) => match n.kind() {
SyntaxKind::CLOSURE_PARAM => {
params.push(lower_main_param(&n, source)?);
}
SyntaxKind::TYPE_NODE if saw_arrow && return_type.is_none() => {
return_type = Some(lower_type_node_from_cst(&n, source)?);
}
_ => continue,
},
}
}
Some(crate::DirectiveBody::Main {
params,
return_type,
})
}
fn lower_main_param(node: &SyntaxNode, source: &str) -> Option<crate::DirectiveMainParam> {
let type_node = node
.children()
.find(|c| c.kind() == SyntaxKind::TYPE_NODE)
.and_then(|n| lower_type_node_from_cst(&n, source))?;
let ident_tok = node
.children_with_tokens()
.filter_map(|el| el.into_token())
.find(|t| t.kind() == SyntaxKind::IDENT)?;
let tr = ident_tok.text_range();
let s: usize = tr.start().into();
let e: usize = tr.end().into();
Some(crate::DirectiveMainParam {
name: ident_tok.text().to_string(),
name_range: range_from_offsets(source, s, e),
type_node,
})
}
enum SchemaColonKey {
SimpleIdent(crate::syntax::SyntaxToken),
TypedDynamic(crate::TypeNode),
}
struct SchemaColonSplit {
directive: crate::Directive,
key: SchemaColonKey,
value: ast::Expr,
}
fn split_schema_colon_directive(node: &SyntaxNode, source: &str) -> Option<SchemaColonSplit> {
let dir_name = node
.children_with_tokens()
.filter_map(|el| el.into_token())
.find(|t| t.kind() == SyntaxKind::IDENT)
.map(|t| t.text().to_string())?;
let shape = crate::directive::directive_shape(&dir_name)?;
if !matches!(shape, crate::DirectiveShape::NameBody) {
return None;
}
let mut after_dir_name = false;
let mut declared_name_tok: Option<crate::syntax::SyntaxToken> = None;
let mut saw_colon = false;
let mut body_expr: Option<ast::Expr> = None;
let mut saw_schema_with = false;
let mut in_generics = false;
let mut generics: Vec<String> = Vec::new();
let mut generics_lt_offset: Option<usize> = None;
let mut generics_gt_end: Option<usize> = None;
for el in node.children_with_tokens() {
match el {
rowan::NodeOrToken::Token(t) => match t.kind() {
SyntaxKind::WHITESPACE
| SyntaxKind::LINE_COMMENT
| SyntaxKind::BLOCK_COMMENT
| SyntaxKind::HASH => continue,
SyntaxKind::IDENT => {
if !after_dir_name {
after_dir_name = true;
continue;
}
if declared_name_tok.is_none() {
declared_name_tok = Some(t);
continue;
}
if in_generics {
generics.push(t.text().to_string());
}
}
SyntaxKind::LT => {
in_generics = true;
generics_lt_offset = Some(t.text_range().start().into());
}
SyntaxKind::GT => {
in_generics = false;
generics_gt_end = Some(t.text_range().end().into());
}
SyntaxKind::COLON if declared_name_tok.is_some() && body_expr.is_none() => {
saw_colon = true;
}
_ => {}
},
rowan::NodeOrToken::Node(n) => {
if n.kind() == SyntaxKind::SCHEMA_WITH {
saw_schema_with = true;
} else if let Some(e) = ast::Expr::cast(n) {
if body_expr.is_none() {
body_expr = Some(e);
}
}
}
}
}
if !saw_colon || saw_schema_with {
return None;
}
let key_token = declared_name_tok?;
let body = body_expr?;
let raw_start: usize = node.text_range().start().into();
let key_start: usize = key_token.text_range().start().into();
let raw_slice = source.get(raw_start..key_start)?;
let trim = trim_leading_trivia(raw_slice);
let dir_start = raw_start + trim;
let dir_end = key_start;
let bare = crate::Directive {
name: dir_name,
body: crate::DirectiveBody::Bare,
range: range_from_offsets(source, dir_start, dir_end),
};
let key = if !generics.is_empty() {
let key_end = generics_gt_end.unwrap_or_else(|| key_token.text_range().end().into());
let name_range = range_from_offsets(source, key_start, key_token.text_range().end().into());
let mut g_nodes: Vec<crate::TypeNode> = Vec::new();
for g_name in generics {
g_nodes.push(crate::TypeNode {
path: vec![g_name],
generics: Vec::new(),
is_optional: false,
range: name_range,
variant_fields: None,
doc_comment: None,
});
}
SchemaColonKey::TypedDynamic(crate::TypeNode {
path: vec![key_token.text().to_string()],
generics: g_nodes,
is_optional: false,
range: range_from_offsets(source, key_start, key_end),
variant_fields: None,
doc_comment: None,
})
} else {
SchemaColonKey::SimpleIdent(key_token)
};
let _ = generics_lt_offset;
Some(SchemaColonSplit {
directive: bare,
key,
value: body,
})
}
fn lower_directive_name_body(node: &SyntaxNode, source: &str) -> Option<crate::DirectiveBody> {
let mut after_dir_name = false;
let mut declared_name: Option<(String, TokenRange)> = None;
let mut in_generics = false;
let mut generics: Vec<String> = Vec::new();
let mut body_expr_ast: Option<ast::Expr> = None;
let mut schema_with: Option<SyntaxNode> = None;
let mut saw_colon_after_name = false;
for el in node.children_with_tokens() {
match el {
rowan::NodeOrToken::Token(t) => match t.kind() {
SyntaxKind::WHITESPACE
| SyntaxKind::LINE_COMMENT
| SyntaxKind::BLOCK_COMMENT
| SyntaxKind::HASH
| SyntaxKind::COMMA => continue,
SyntaxKind::COLON => {
if declared_name.is_some() && body_expr_ast.is_none() {
saw_colon_after_name = true;
}
}
SyntaxKind::IDENT => {
if !after_dir_name {
after_dir_name = true;
continue;
}
if declared_name.is_none() {
let tr = t.text_range();
let s: usize = tr.start().into();
let e: usize = tr.end().into();
declared_name =
Some((t.text().to_string(), range_from_offsets(source, s, e)));
continue;
}
if in_generics {
generics.push(t.text().to_string());
continue;
}
}
SyntaxKind::LT => in_generics = true,
SyntaxKind::GT => in_generics = false,
_ => continue,
},
rowan::NodeOrToken::Node(n) => match n.kind() {
SyntaxKind::SCHEMA_WITH => schema_with = Some(n),
_ => {
if let Some(e) = ast::Expr::cast(n) {
if body_expr_ast.is_none() {
body_expr_ast = Some(e);
}
}
}
},
}
}
if saw_colon_after_name && schema_with.is_none() {
return Some(crate::DirectiveBody::Bare);
}
let (name, name_range) = match declared_name {
Some(n) => n,
None => return Some(crate::DirectiveBody::Bare),
};
let body = if let Some(expr) = body_expr_ast {
Box::new(lower_expr_v2(&expr, source)?)
} else {
let pos = if let Some(sw) = &schema_with {
let mut last_ident_start: Option<usize> = None;
for el in node.children_with_tokens() {
match el {
rowan::NodeOrToken::Token(t) => {
if t.kind() == SyntaxKind::IDENT && t.text() == "with" {
last_ident_start = Some(t.text_range().start().into());
}
}
rowan::NodeOrToken::Node(n) => {
if n.kind() == SyntaxKind::SCHEMA_WITH {
break;
}
}
}
}
last_ident_start.unwrap_or_else(|| sw.text_range().start().into())
} else {
let r = node.text_range();
let end: usize = r.end().into();
end
};
Box::new(crate::Node {
id: crate::NodeId::alloc(),
expr: std::sync::Arc::new(crate::Expr::Dict(Vec::new())),
decorators: Vec::new(),
directives: Vec::new(),
type_hint: None,
range: range_from_offsets(source, pos, pos),
doc_comment: None,
})
};
let (methods, schema_no_auto_derives) = if let Some(sw) = schema_with {
lower_schema_with(&sw, source)?
} else {
(Vec::new(), Vec::new())
};
Some(crate::DirectiveBody::NameBody {
name,
name_range,
generics,
body,
methods,
schema_no_auto_derives,
})
}
fn lower_directive_enum_body(node: &SyntaxNode, source: &str) -> Option<crate::DirectiveBody> {
let mut after_dir_name = false;
let mut declared_name: Option<(String, TokenRange)> = None;
let mut in_generics = false;
let mut before_body = true;
let mut generics: Vec<String> = Vec::new();
for el in node.children_with_tokens() {
let rowan::NodeOrToken::Token(t) = el else {
continue;
};
match t.kind() {
SyntaxKind::WHITESPACE
| SyntaxKind::LINE_COMMENT
| SyntaxKind::BLOCK_COMMENT
| SyntaxKind::HASH
| SyntaxKind::COMMA => continue,
SyntaxKind::L_BRACE => {
before_body = false;
in_generics = false;
}
SyntaxKind::IDENT => {
if !after_dir_name {
after_dir_name = true;
continue;
}
if declared_name.is_none() {
let tr = t.text_range();
let s: usize = tr.start().into();
let e: usize = tr.end().into();
declared_name = Some((t.text().to_string(), range_from_offsets(source, s, e)));
continue;
}
if before_body && in_generics {
generics.push(t.text().to_string());
}
}
SyntaxKind::LT if before_body => in_generics = true,
SyntaxKind::GT if before_body => in_generics = false,
_ => {}
}
}
let (name, name_range) = declared_name?;
let mut variants = Vec::new();
for child in node
.children()
.filter(|c| c.kind() == SyntaxKind::ENUM_VARIANT)
{
variants.push(lower_enum_directive_variant(&child, source)?);
}
let r = node.text_range();
let start: usize = r.start().into();
let end: usize = r.end().into();
let range = range_from_offsets(source, start, end);
let enum_type = crate::TypeNode {
path: vec![crate::INTERNAL_ENUM_TYPE_NAME.to_string()],
generics: variants,
is_optional: false,
range,
variant_fields: None,
doc_comment: None,
};
let body = Box::new(crate::Node {
id: crate::NodeId::alloc(),
expr: std::sync::Arc::new(crate::Expr::Type(enum_type)),
decorators: Vec::new(),
directives: Vec::new(),
type_hint: None,
range,
doc_comment: None,
});
Some(crate::DirectiveBody::NameBody {
name,
name_range,
generics,
body,
methods: Vec::new(),
schema_no_auto_derives: Vec::new(),
})
}
fn lower_enum_directive_variant(node: &SyntaxNode, source: &str) -> Option<crate::TypeNode> {
let name_token = node
.children_with_tokens()
.filter_map(|el| el.into_token())
.find(|t| t.kind() == SyntaxKind::IDENT)?;
let name = name_token.text().to_string();
let mut fields: Vec<(String, crate::TypeNode)> = Vec::new();
for field_node in node
.children()
.filter(|c| c.kind() == SyntaxKind::ENUM_VARIANT_FIELD)
{
let field_name = field_node
.children_with_tokens()
.filter_map(|el| el.into_token())
.find(|t| t.kind() == SyntaxKind::IDENT)
.map(|t| t.text().to_string())?;
let ty = field_node.children().find_map(|c| match c.kind() {
SyntaxKind::TYPE_NODE | SyntaxKind::TUPLE_TYPE => lower_type_node_from_cst(&c, source),
_ => None,
})?;
fields.push((field_name, ty));
}
if let Some(tuple_node) = node.children().find(|c| c.kind() == SyntaxKind::TUPLE_TYPE) {
let tuple_ty = lower_type_node_from_cst(&tuple_node, source)?;
for (idx, ty) in tuple_ty.generics.into_iter().enumerate() {
fields.push((idx.to_string(), ty));
}
}
let r = node.text_range();
let start: usize = r.start().into();
let end: usize = r.end().into();
Some(crate::TypeNode {
path: vec![name],
generics: Vec::new(),
is_optional: false,
range: range_from_offsets(source, start, end),
variant_fields: Some(fields),
doc_comment: None,
})
}
fn lower_schema_with(
node: &SyntaxNode,
source: &str,
) -> Option<(Vec<crate::SchemaMethod>, Vec<String>)> {
let mut methods: Vec<crate::SchemaMethod> = Vec::new();
let mut schema_no_auto_derives: Vec<String> = Vec::new();
for child in node.children() {
match child.kind() {
SyntaxKind::SCHEMA_METHOD => {
let (method, method_no_auto_derives) = lower_schema_method(&child, source)?;
schema_no_auto_derives.extend(method_no_auto_derives);
methods.push(method);
}
SyntaxKind::DIRECTIVE => {
let name = child
.children_with_tokens()
.filter_map(|el| el.into_token())
.find(|t| t.kind() == SyntaxKind::IDENT)
.map(|t| t.text().to_string());
if name.as_deref() == Some(crate::directive::NO_AUTO_DERIVE) {
if let Some(constraint) = directive_constraint_name(&child) {
schema_no_auto_derives.push(constraint);
} else {
return None;
}
}
}
_ => continue,
}
}
Some((methods, schema_no_auto_derives))
}
fn lower_schema_method(
node: &SyntaxNode,
source: &str,
) -> Option<(crate::SchemaMethod, Vec<String>)> {
let method_start = method_name_offset(node);
let method_end: usize = node.text_range().end().into();
let mut derives: Vec<String> = Vec::new();
let mut schema_no_auto_derives: Vec<String> = Vec::new();
let mut is_native = false;
let mut is_private = false;
let mut name: Option<(String, TokenRange)> = None;
let mut method_generics: Vec<String> = Vec::new();
let mut params: Vec<crate::SchemaMethodParam> = Vec::new();
let mut return_type: Option<crate::TypeNode> = None;
let mut body: Option<Box<crate::Node>> = None;
let mut saw_arrow = false;
let mut in_generics = false;
let mut after_body_colon = false;
let mut body_after_colon_ast: Option<ast::Expr> = None;
for el in node.children_with_tokens() {
match el {
rowan::NodeOrToken::Token(t) => match t.kind() {
SyntaxKind::WHITESPACE
| SyntaxKind::LINE_COMMENT
| SyntaxKind::BLOCK_COMMENT
| SyntaxKind::L_PAREN
| SyntaxKind::R_PAREN
| SyntaxKind::COMMA => continue,
SyntaxKind::IDENT => {
if name.is_none() {
let tr = t.text_range();
let s: usize = tr.start().into();
let e: usize = tr.end().into();
name = Some((t.text().to_string(), range_from_offsets(source, s, e)));
continue;
}
if in_generics {
method_generics.push(t.text().to_string());
}
}
SyntaxKind::LT => in_generics = true,
SyntaxKind::GT => in_generics = false,
SyntaxKind::THIN_ARROW => saw_arrow = true,
SyntaxKind::COLON => after_body_colon = true,
_ => continue,
},
rowan::NodeOrToken::Node(n) => match n.kind() {
SyntaxKind::DIRECTIVE => {
let dname = n
.children_with_tokens()
.filter_map(|el| el.into_token())
.find(|t| t.kind() == SyntaxKind::IDENT)
.map(|t| t.text().to_string());
match dname.as_deref() {
Some(crate::directive::DERIVE) => {
derives.push(directive_constraint_name(&n)?);
}
Some(crate::directive::NATIVE) => is_native = true,
Some(crate::directive::INTERNAL) => is_private = true,
Some(crate::directive::NO_AUTO_DERIVE) => {
schema_no_auto_derives.push(directive_constraint_name(&n)?);
}
_ => return None,
}
}
SyntaxKind::CLOSURE_PARAM => {
params.push(lower_schema_method_param(&n, source)?);
}
SyntaxKind::TYPE_NODE if saw_arrow && return_type.is_none() => {
return_type = Some(lower_type_node_from_cst(&n, source)?);
}
_ => {
if after_body_colon {
if let Some(e) = ast::Expr::cast(n) {
if body_after_colon_ast.is_none() {
body_after_colon_ast = Some(e);
}
}
}
}
},
}
}
if let Some(e) = body_after_colon_ast {
body = Some(Box::new(lower_expr_v2(&e, source)?));
}
if is_native && body.is_some() {
return None;
}
if !is_native && body.is_none() {
return None;
}
let (name, name_range) = name?;
let return_type = return_type?;
Some((
crate::SchemaMethod {
name,
name_range,
generics: method_generics,
params,
return_type,
body,
derives,
is_native,
is_private,
range: range_from_offsets(source, method_start, method_end),
doc_comment: None,
},
schema_no_auto_derives,
))
}
fn method_name_offset(node: &SyntaxNode) -> usize {
for el in node.children_with_tokens() {
match el {
rowan::NodeOrToken::Token(t) => match t.kind() {
SyntaxKind::WHITESPACE | SyntaxKind::LINE_COMMENT | SyntaxKind::BLOCK_COMMENT => {
continue
}
SyntaxKind::IDENT => return t.text_range().start().into(),
_ => continue,
},
rowan::NodeOrToken::Node(n) => {
if n.kind() == SyntaxKind::DIRECTIVE {
continue;
}
break;
}
}
}
node.text_range().start().into()
}
fn lower_schema_method_param(node: &SyntaxNode, source: &str) -> Option<crate::SchemaMethodParam> {
let name_tok = node
.children_with_tokens()
.filter_map(|el| el.into_token())
.find(|t| t.kind() == SyntaxKind::IDENT)?;
let tr = name_tok.text_range();
let s: usize = tr.start().into();
let e: usize = tr.end().into();
let type_node = node
.children()
.find(|c| c.kind() == SyntaxKind::TYPE_NODE)
.and_then(|n| lower_type_node_from_cst(&n, source))?;
Some(crate::SchemaMethodParam {
name: name_tok.text().to_string(),
name_range: range_from_offsets(source, s, e),
type_node,
})
}
fn directive_constraint_name(node: &SyntaxNode) -> Option<String> {
let body_expr = node.children().find_map(ast::Expr::cast)?;
if let ast::Expr::Variable(v) = body_expr {
let segs = v.segments();
if segs.len() == 1 {
return Some(segs.into_iter().next().unwrap());
}
}
None
}
fn lower_type_node_from_cst(node: &SyntaxNode, source: &str) -> Option<crate::TypeNode> {
let r = node.text_range();
let start: usize = first_non_trivia_offset(node).unwrap_or_else(|| r.start().into());
let end: usize = r.end().into();
if node.kind() == SyntaxKind::TUPLE_TYPE {
let mut elems: Vec<crate::TypeNode> = Vec::new();
for child in node.children() {
if let Some(t) = lower_type_node_from_cst(&child, source) {
elems.push(t);
}
}
return Some(crate::TypeNode {
path: vec!["Tuple".to_string()],
generics: elems,
is_optional: false,
range: range_from_offsets(source, start, end),
variant_fields: None,
doc_comment: None,
});
}
if node.kind() != SyntaxKind::TYPE_NODE {
return None;
}
let mut path: Vec<String> = Vec::new();
let mut in_generics = false;
let mut after_path = false;
let mut generics: Vec<crate::TypeNode> = Vec::new();
for el in node.children_with_tokens() {
match el {
rowan::NodeOrToken::Token(t) => match t.kind() {
SyntaxKind::WHITESPACE | SyntaxKind::LINE_COMMENT | SyntaxKind::BLOCK_COMMENT => {
continue
}
SyntaxKind::DOT => continue,
SyntaxKind::IDENT | SyntaxKind::STRING if !after_path => {
let txt = if t.kind() == SyntaxKind::STRING {
parse_string_text(t.text())?
} else {
t.text().to_string()
};
path.push(txt);
}
SyntaxKind::LT => {
after_path = true;
in_generics = true;
}
SyntaxKind::GT => {
in_generics = false;
}
SyntaxKind::QUESTION => continue,
_ => continue,
},
rowan::NodeOrToken::Node(n) => {
if in_generics && matches!(n.kind(), SyntaxKind::TYPE_NODE | SyntaxKind::TUPLE_TYPE)
{
generics.push(lower_type_node_from_cst(&n, source)?);
}
}
}
}
if path.is_empty() {
return None;
}
Some(crate::TypeNode {
path,
generics,
is_optional: false,
range: range_from_offsets(source, start, end),
variant_fields: None,
doc_comment: None,
})
}
fn first_non_trivia_offset(node: &SyntaxNode) -> Option<usize> {
for el in node.children_with_tokens() {
match el {
rowan::NodeOrToken::Token(t) => match t.kind() {
SyntaxKind::WHITESPACE | SyntaxKind::LINE_COMMENT | SyntaxKind::BLOCK_COMMENT => {
continue
}
_ => return Some(t.text_range().start().into()),
},
rowan::NodeOrToken::Node(n) => return Some(n.text_range().start().into()),
}
}
None
}
#[allow(dead_code)]
fn lower_decorator_v2(dec: &ast::Decorator, source: &str) -> Option<crate::Decorator> {
let node = dec.syntax();
let r = node.text_range();
let raw_start: usize = r.start().into();
let end: usize = r.end().into();
let raw_slice = source.get(raw_start..end)?;
let trim = trim_leading_trivia(raw_slice);
let start = raw_start + trim;
let mut path: Vec<TokenKey> = Vec::new();
let mut args: Vec<crate::CallArg> = Vec::new();
let mut head_done = false;
let mut expect_segment_after_dot = false;
for el in node.children_with_tokens() {
match el {
rowan::NodeOrToken::Token(t) => match t.kind() {
SyntaxKind::WHITESPACE
| SyntaxKind::LINE_COMMENT
| SyntaxKind::BLOCK_COMMENT
| SyntaxKind::AT => continue,
SyntaxKind::IDENT => {
let tr = t.text_range();
let s: usize = tr.start().into();
let e: usize = tr.end().into();
let r = range_from_offsets(source, s, e);
if !head_done {
path.push(TokenKey::String(t.text().to_string(), r, false));
head_done = true;
} else if expect_segment_after_dot {
path.push(TokenKey::String(t.text().to_string(), r, false));
expect_segment_after_dot = false;
} else {
return None;
}
}
SyntaxKind::DOT => {
expect_segment_after_dot = true;
}
_ => continue,
},
rowan::NodeOrToken::Node(n) => {
if n.kind() == SyntaxKind::CALL_ARG {
args.extend(walk_call_arg_node(&n, source)?);
}
}
}
}
let mut saw_named = false;
for a in &args {
if a.name.is_some() {
saw_named = true;
} else if saw_named {
return None;
}
}
Some(crate::Decorator {
path,
args,
range: range_from_offsets(source, start, end),
})
}
fn walk_call_arg_node(node: &SyntaxNode, source: &str) -> Option<Vec<crate::CallArg>> {
let mut args: Vec<crate::CallArg> = Vec::new();
let mut pending_name: Option<String> = None;
let mut iter = node.children_with_tokens().peekable();
while let Some(el) = iter.next() {
match el {
rowan::NodeOrToken::Token(t) => match t.kind() {
SyntaxKind::WHITESPACE
| SyntaxKind::LINE_COMMENT
| SyntaxKind::BLOCK_COMMENT
| SyntaxKind::L_PAREN
| SyntaxKind::R_PAREN
| SyntaxKind::COMMA => continue,
SyntaxKind::IDENT => {
let name = t.text().to_string();
let mut after = iter.clone();
let mut found_eq = false;
for nx in after.by_ref() {
if let Some(tt) = nx.as_token() {
match tt.kind() {
SyntaxKind::WHITESPACE
| SyntaxKind::LINE_COMMENT
| SyntaxKind::BLOCK_COMMENT => continue,
SyntaxKind::EQ => {
found_eq = true;
break;
}
_ => break,
}
} else {
break;
}
}
if found_eq {
while let Some(peek) = iter.peek() {
let is_eq_or_trivia = peek
.as_token()
.map(|tt| {
matches!(
tt.kind(),
SyntaxKind::WHITESPACE
| SyntaxKind::LINE_COMMENT
| SyntaxKind::BLOCK_COMMENT
| SyntaxKind::EQ
)
})
.unwrap_or(false);
if is_eq_or_trivia {
let eaten = iter.next();
if eaten
.as_ref()
.and_then(|e| e.as_token())
.map(|tt| tt.kind() == SyntaxKind::EQ)
.unwrap_or(false)
{
break;
}
} else {
break;
}
}
pending_name = Some(name);
} else {
return None;
}
}
_ => continue,
},
rowan::NodeOrToken::Node(n) => {
if let Some(expr) = ast::Expr::cast(n) {
let value = lower_expr_v2(&expr, source)?;
args.push(crate::CallArg {
name: pending_name.take(),
value,
});
}
}
}
}
Some(args)
}
fn lower_spread_expr_v2(node: &SyntaxNode, source: &str) -> Option<Node> {
let r = node.text_range();
let start: usize = r.start().into();
let end: usize = r.end().into();
let mut type_hint: Option<crate::TypeNode> = None;
let mut inner_expr: Option<ast::Expr> = None;
for child in node.children() {
if child.kind() == SyntaxKind::TYPE_NODE && type_hint.is_none() && inner_expr.is_none() {
type_hint = Some(lower_type_node_from_cst(&child, source)?);
continue;
}
if let Some(e) = ast::Expr::cast(child) {
inner_expr = Some(e);
break;
}
}
let inner_ast = inner_expr?;
let mut inner = lower_expr_v2(&inner_ast, source)?;
if let Some(th) = type_hint {
inner.type_hint = Some(th);
}
Some(Node::new(
Expr::Spread(inner),
range_from_offsets(source, start, end),
))
}
fn lower_type_expr_v2(node: &SyntaxNode, source: &str) -> Option<Node> {
let t = lower_type_node_from_cst(node, source)?;
let range = t.range;
Some(Node::new(Expr::Type(t), range))
}
fn lower_unary_expr_v2(node: &SyntaxNode, source: &str) -> Option<Node> {
let r = node.text_range();
let start: usize = r.start().into();
let end: usize = r.end().into();
let op_token = node
.children_with_tokens()
.filter_map(|el| el.into_token())
.find(|t| {
matches!(
t.kind(),
SyntaxKind::MINUS | SyntaxKind::BANG | SyntaxKind::PLUS
)
})?;
let operand_ast = node.children().find_map(ast::Expr::cast)?;
let operand = lower_expr_v2(&operand_ast, source)?;
let op = match op_token.kind() {
SyntaxKind::MINUS => crate::Operator::Sub,
SyntaxKind::BANG => crate::Operator::Not,
SyntaxKind::PLUS => {
return None;
}
_ => return None,
};
Some(Node::new(
Expr::Unary(op, operand),
range_from_offsets(source, start, end),
))
}
fn lower_dict_v2(node: &SyntaxNode, source: &str) -> Option<Node> {
let start: usize = node
.children_with_tokens()
.find_map(|el| {
el.into_token().and_then(|t| {
if t.kind() == SyntaxKind::L_BRACE {
Some(t.text_range().start().into())
} else {
None
}
})
})
.unwrap_or_else(|| node.text_range().start().into());
let end: usize = node.text_range().end().into();
let mut pairs: Vec<(TokenKey, Node)> = Vec::new();
let mut standalone_directives: Vec<crate::Directive> = Vec::new();
let fields: Vec<SyntaxNode> = node
.children()
.filter(|c| c.kind() == SyntaxKind::DICT_FIELD)
.collect();
let total = fields.len();
for (idx, field) in fields.into_iter().enumerate() {
let is_empty = field
.children_with_tokens()
.filter_map(|el| match el {
rowan::NodeOrToken::Token(t) => Some(t),
rowan::NodeOrToken::Node(_) => None,
})
.all(|t| {
matches!(
t.kind(),
SyntaxKind::WHITESPACE | SyntaxKind::LINE_COMMENT | SyntaxKind::BLOCK_COMMENT
)
})
&& field.children().next().is_none();
if is_empty {
if idx + 1 == total {
continue;
}
if is_recovering() {
continue;
}
return None;
}
match lower_dict_field(&field, source) {
Some(DictFieldOut::Pair(k, v)) => pairs.push((k, v)),
Some(DictFieldOut::Directives(dirs)) => standalone_directives.extend(dirs),
None if is_recovering() => continue,
None => return None,
}
}
let mut out = Node::new(Expr::Dict(pairs), range_from_offsets(source, start, end));
out.directives = standalone_directives;
Some(out)
}
#[allow(clippy::large_enum_variant)]
enum DictFieldOut {
Pair(TokenKey, Node),
Directives(Vec<crate::Directive>),
}
fn lower_dict_field(node: &SyntaxNode, source: &str) -> Option<DictFieldOut> {
let mut decorators_before: Vec<crate::Decorator> = Vec::new();
let mut directives_before: Vec<crate::Directive> = Vec::new();
let field_start: usize = node.text_range().start().into();
let doc_comment: Option<String> = first_non_trivia_offset(node)
.filter(|end_off| *end_off > field_start)
.and_then(|end_off| {
let leading_slice = &source[field_start..end_off];
crate::parse_leading_comments(leading_slice).0
});
let mut spread_node: Option<SyntaxNode> = None;
let mut type_hint: Option<crate::TypeNode> = None;
let mut key_token: Option<crate::syntax::SyntaxToken> = None; let mut dynamic_key_node: Option<SyntaxNode> = None; let mut dynamic_key_type: Option<crate::TypeNode> = None; let mut value_expr_ast: Option<ast::Expr> = None;
let mut closure_node: Option<SyntaxNode> = None;
let mut in_brack = false;
let mut after_lt = false;
let mut saw_dict_field_colon = false;
let mut prebuilt_key: Option<TokenKey> = None;
let child_iter = node.children_with_tokens();
for el in child_iter {
match el {
rowan::NodeOrToken::Token(t) => match t.kind() {
SyntaxKind::WHITESPACE | SyntaxKind::LINE_COMMENT | SyntaxKind::BLOCK_COMMENT => {
continue
}
SyntaxKind::L_BRACK => {
in_brack = true;
}
SyntaxKind::R_BRACK => {
in_brack = false;
}
SyntaxKind::LT if in_brack => {
after_lt = true;
}
SyntaxKind::GT if in_brack => {
after_lt = false;
}
SyntaxKind::IDENT | SyntaxKind::STRING => {
if !in_brack && key_token.is_none() && value_expr_ast.is_none() {
key_token = Some(t);
}
}
SyntaxKind::COLON => {
if !in_brack {
saw_dict_field_colon = true;
}
}
_ => continue,
},
rowan::NodeOrToken::Node(n) => match n.kind() {
SyntaxKind::SPREAD_EXPR => {
spread_node = Some(n);
}
SyntaxKind::DECORATOR => {
if let Some(dec) = ast::Decorator::cast(n) {
decorators_before.push(lower_decorator_v2(&dec, source)?);
}
}
SyntaxKind::DIRECTIVE => {
if let Some(dir) = ast::Directive::cast(n.clone()) {
if let Some(split) = split_schema_colon_directive(&n, source) {
directives_before.push(split.directive);
match split.key {
SchemaColonKey::SimpleIdent(tok) => {
key_token = Some(tok);
}
SchemaColonKey::TypedDynamic(type_node) => {
let range = type_node.range;
prebuilt_key = Some(TokenKey::Dynamic(
Node::new(Expr::Type(type_node), range),
false,
));
}
}
value_expr_ast = Some(split.value);
continue;
}
directives_before.push(lower_directive_v2(&dir, source)?);
}
}
SyntaxKind::TYPE_NODE => {
if in_brack && after_lt {
dynamic_key_type = Some(lower_type_node_from_cst(&n, source)?);
} else if !in_brack && type_hint.is_none() && key_token.is_none() {
type_hint = Some(lower_type_node_from_cst(&n, source)?);
} else if !in_brack && saw_dict_field_colon && value_expr_ast.is_none() {
if let Some(e) = ast::Expr::cast(n) {
value_expr_ast = Some(e);
}
}
}
SyntaxKind::TUPLE_TYPE => {
if !in_brack && type_hint.is_none() && key_token.is_none() {
type_hint = Some(lower_type_node_from_cst(&n, source)?);
}
}
SyntaxKind::CLOSURE => {
if saw_dict_field_colon {
if value_expr_ast.is_none() && !in_brack {
if let Some(e) = ast::Expr::cast(n) {
value_expr_ast = Some(e);
}
}
} else if closure_node.is_none() {
closure_node = Some(n);
}
}
_ => {
if let Some(e) = ast::Expr::cast(n) {
if in_brack && dynamic_key_node.is_none() {
dynamic_key_node = Some(e.syntax().clone());
} else if value_expr_ast.is_none() && !in_brack {
value_expr_ast = Some(e);
}
}
}
},
}
}
if let Some(sn) = spread_node {
let s_r = sn.text_range();
let s_start: usize = s_r.start().into();
let mut ellipsis_end = s_start + 3;
for el in sn.children_with_tokens() {
if let Some(t) = el.into_token() {
if t.kind() == SyntaxKind::ELLIPSIS {
ellipsis_end = t.text_range().end().into();
break;
}
}
}
let mut inner_type: Option<crate::TypeNode> = None;
let mut inner_expr_ast: Option<ast::Expr> = None;
let mut saw_lt = false;
for el in sn.children_with_tokens() {
match el {
rowan::NodeOrToken::Token(t) => match t.kind() {
SyntaxKind::LT => saw_lt = true,
SyntaxKind::GT => saw_lt = false,
_ => {}
},
rowan::NodeOrToken::Node(n) => {
if n.kind() == SyntaxKind::TYPE_NODE && saw_lt {
inner_type = Some(lower_type_node_from_cst(&n, source)?);
} else if let Some(e) = ast::Expr::cast(n) {
if inner_expr_ast.is_none() {
inner_expr_ast = Some(e);
}
}
}
}
}
let inner_ast = inner_expr_ast?;
let mut inner = lower_expr_v2(&inner_ast, source)?;
if let Some(t) = inner_type {
inner.type_hint = Some(t);
}
return Some(DictFieldOut::Pair(
TokenKey::Spread(range_from_offsets(source, s_start, ellipsis_end)),
inner,
));
}
if key_token.is_none()
&& dynamic_key_node.is_none()
&& value_expr_ast.is_none()
&& closure_node.is_none()
{
if decorators_before.is_empty() {
return Some(DictFieldOut::Directives(directives_before));
}
return None;
}
let key = if let Some(pk) = prebuilt_key {
pk
} else if let Some(dyn_node) = dynamic_key_node {
let dyn_ast = ast::Expr::cast(dyn_node)?;
let mut inner = lower_expr_v2(&dyn_ast, source)?;
if let Some(t) = dynamic_key_type {
inner.type_hint = Some(t);
}
TokenKey::Dynamic(inner, false)
} else {
let kt = key_token?;
let tr = kt.text_range();
let s: usize = tr.start().into();
let e: usize = tr.end().into();
let key_range = range_from_offsets(source, s, e);
match kt.kind() {
SyntaxKind::IDENT => TokenKey::String(kt.text().to_string(), key_range, false),
SyntaxKind::STRING => {
let decoded = parse_string_text(kt.text())?;
TokenKey::String(decoded, key_range, false)
}
_ => return None,
}
};
let value = if let Some(cls) = closure_node {
let cls_node = lower_closure_v2(&cls, source)?;
let mut cls_node = cls_node;
let body_range_opt: Option<TokenRange> = {
let mut last_body: Option<TokenRange> = None;
for child in cls.children() {
if matches!(
child.kind(),
SyntaxKind::CLOSURE_PARAM | SyntaxKind::TYPE_NODE
) {
continue;
}
if let Some(e) = ast::Expr::cast(child) {
let body = lower_expr_v2(&e, source)?;
last_body = Some(body.range);
}
}
last_body
};
if let Some(br) = body_range_opt {
cls_node.range = br;
}
let owned_expr = std::sync::Arc::try_unwrap(std::mem::replace(
&mut cls_node.expr,
std::sync::Arc::new(Expr::Missing),
))
.unwrap_or_else(|shared| (*shared).clone());
cls_node.expr = match owned_expr {
Expr::Closure {
params,
return_type,
body,
} => {
let final_return = if type_hint.is_some() {
type_hint.clone()
} else {
return_type
};
std::sync::Arc::new(Expr::Closure {
params,
return_type: final_return,
body,
})
}
other => std::sync::Arc::new(other),
};
cls_node
} else {
let value_ast = value_expr_ast?;
let mut value = lower_expr_v2(&value_ast, source)?;
if type_hint.is_some() {
value.type_hint = type_hint.clone();
}
value
};
let value = value
.with_decorators(decorators_before)
.with_directives(directives_before)
.with_doc_comment(doc_comment);
Some(DictFieldOut::Pair(key, value))
}
fn lower_list_v2(node: &SyntaxNode, source: &str) -> Option<Node> {
let r = node.text_range();
let start: usize = r.start().into();
let end: usize = r.end().into();
let mut items: Vec<Node> = Vec::new();
let mut pending_decs: Vec<crate::Decorator> = Vec::new();
let mut pending_dirs: Vec<crate::Directive> = Vec::new();
for child in node.children() {
match child.kind() {
SyntaxKind::DECORATOR => {
if let Some(d) = ast::Decorator::cast(child.clone()) {
match lower_decorator_v2(&d, source) {
Some(dec) => pending_decs.push(dec),
None if is_recovering() => continue,
None => return None,
}
}
}
SyntaxKind::DIRECTIVE => {
if let Some(d) = ast::Directive::cast(child.clone()) {
match lower_directive_v2(&d, source) {
Some(dir) => pending_dirs.push(dir),
None if is_recovering() => continue,
None => return None,
}
}
}
_ => {
if let Some(e) = ast::Expr::cast(child.clone()) {
let item_opt = lower_expr_v2(&e, source);
let mut item = match item_opt {
Some(n) => n,
None if is_recovering() => {
let r = child.text_range();
let start_o: usize = r.start().into();
let end_o: usize = r.end().into();
Node::new(Expr::Missing, range_from_offsets(source, start_o, end_o))
}
None => return None,
};
if !pending_decs.is_empty() {
item.decorators = std::mem::take(&mut pending_decs);
}
if !pending_dirs.is_empty() {
item.directives = std::mem::take(&mut pending_dirs);
}
items.push(item);
}
}
}
}
Some(Node::new(
Expr::List(items),
range_from_offsets(source, start, end),
))
}
fn lower_tuple_v2(node: &SyntaxNode, source: &str) -> Option<Node> {
let r = node.text_range();
let start: usize = r.start().into();
let end: usize = r.end().into();
let mut items: Vec<Node> = Vec::new();
for child in node.children() {
if let Some(e) = ast::Expr::cast(child.clone()) {
let item = match lower_expr_v2(&e, source) {
Some(n) => n,
None if is_recovering() => {
let cr = child.text_range();
let start_o: usize = cr.start().into();
let end_o: usize = cr.end().into();
Node::new(Expr::Missing, range_from_offsets(source, start_o, end_o))
}
None => return None,
};
items.push(item);
}
}
Some(Node::new(
Expr::Tuple(items),
range_from_offsets(source, start, end),
))
}
fn lower_comprehension_v2(node: &SyntaxNode, source: &str) -> Option<Node> {
let r = node.text_range();
let start: usize = r.start().into();
let end: usize = r.end().into();
let mut id_text: Option<String> = None;
let mut after_for = false;
for el in node.children_with_tokens() {
if let Some(t) = el.into_token() {
if t.kind() == SyntaxKind::IDENT {
let txt = t.text();
if after_for && id_text.is_none() {
id_text = Some(txt.to_string());
} else if txt == "for" {
after_for = true;
}
}
}
}
let id = id_text?;
let mut exprs = node.children().filter_map(ast::Expr::cast);
let element_ast = exprs.next()?;
let iterable_ast = exprs.next()?;
let condition_ast = exprs.next();
let element = lower_expr_v2(&element_ast, source)?;
let iterable = lower_expr_v2(&iterable_ast, source)?;
let condition = match condition_ast {
Some(c) => Some(lower_expr_v2(&c, source)?),
None => None,
};
Some(Node::new(
Expr::Comprehension {
element,
id,
iterable,
condition,
},
range_from_offsets(source, start, end),
))
}
fn decode_fstring_literal(text: &str, raw: bool) -> Option<String> {
if raw {
return Some(text.to_string());
}
let mut out = String::with_capacity(text.len());
let mut chars = text.chars().peekable();
while let Some(c) = chars.next() {
if c != '\\' {
out.push(c);
continue;
}
let escape = chars.next()?;
match escape {
'n' => out.push('\n'),
'r' => out.push('\r'),
't' => out.push('\t'),
'b' => out.push('\u{08}'),
'f' => out.push('\u{0C}'),
'\\' => out.push('\\'),
'/' => out.push('/'),
'"' => out.push('"'),
'u' => {
let cp = if chars.peek().copied() == Some('{') {
chars.next();
let mut hex = String::new();
loop {
let h = chars.next()?;
if h == '}' {
break;
}
if !h.is_ascii_hexdigit() {
return None;
}
hex.push(h);
}
if hex.is_empty() || hex.len() > 6 {
return None;
}
u32::from_str_radix(&hex, 16).ok()?
} else {
let mut hex = String::new();
for _ in 0..4 {
let h = chars.next()?;
if !h.is_ascii_hexdigit() {
return None;
}
hex.push(h);
}
u32::from_str_radix(&hex, 16).ok()?
};
out.push(std::char::from_u32(cp)?);
}
w if w.is_whitespace() => {
while let Some(&peek) = chars.peek() {
if peek.is_whitespace() {
chars.next();
} else {
break;
}
}
}
_ => return None,
}
}
Some(out)
}
fn lower_fstring_v2(node: &SyntaxNode, source: &str) -> Option<Node> {
let r = node.text_range();
let start: usize = r.start().into();
let end: usize = r.end().into();
let raw = node
.children_with_tokens()
.filter_map(|el| el.into_token())
.find(|t| t.kind() == SyntaxKind::F_STRING_OPEN)
.map(|t| t.text().contains('#'))
.unwrap_or(false);
let mut parts: Vec<crate::FStringPart> = Vec::new();
for el in node.children_with_tokens() {
match el {
rowan::NodeOrToken::Token(t) => {
if t.kind() == SyntaxKind::F_STRING_LITERAL {
let decoded = decode_fstring_literal(t.text(), raw)?;
if decoded.is_empty() {
continue;
}
if let Some(crate::FStringPart::Literal(ref mut last)) = parts.last_mut() {
last.push_str(&decoded);
} else {
parts.push(crate::FStringPart::Literal(decoded));
}
}
}
rowan::NodeOrToken::Node(n) => {
if n.kind() == SyntaxKind::F_STRING_INTERPOLATION {
let inner_ast = n.children().find_map(ast::Expr::cast)?;
let inner = lower_expr_v2(&inner_ast, source)?;
parts.push(crate::FStringPart::Interpolation(Box::new(inner)));
}
}
}
}
Some(Node::new(
Expr::FString(parts),
range_from_offsets(source, start, end),
))
}
fn lower_variant_ctor_v2(node: &SyntaxNode, source: &str) -> Option<Node> {
let r = node.text_range();
let start: usize = r.start().into();
let end: usize = r.end().into();
let mut path: Vec<String> = Vec::new();
for el in node.children_with_tokens() {
if let Some(t) = el.into_token() {
if t.kind() == SyntaxKind::IDENT {
path.push(t.text().to_string());
}
}
}
if path.len() < 2 {
return None;
}
let variant = path.pop().unwrap();
let body_dict = node.children().find(|c| c.kind() == SyntaxKind::DICT)?;
let body_expr = ast::Expr::cast(body_dict)?;
let body = lower_expr_v2(&body_expr, source)?;
Some(Node::new(
Expr::VariantCtor {
enum_path: path,
variant,
body,
},
range_from_offsets(source, start, end),
))
}
fn lower_closure_param_v2(node: &SyntaxNode, source: &str) -> Option<crate::ClosureParam> {
let r = node.text_range();
let start: usize = node
.children_with_tokens()
.find_map(|el| match el {
rowan::NodeOrToken::Token(t)
if matches!(
t.kind(),
SyntaxKind::WHITESPACE | SyntaxKind::LINE_COMMENT | SyntaxKind::BLOCK_COMMENT
) =>
{
None
}
rowan::NodeOrToken::Token(t) => Some(t.text_range().start().into()),
rowan::NodeOrToken::Node(n) => Some(n.text_range().start().into()),
})
.unwrap_or_else(|| r.start().into());
let end: usize = r.end().into();
let type_hint = node
.children()
.find(|c| c.kind() == SyntaxKind::TYPE_NODE)
.and_then(|n| lower_type_node_from_cst(&n, source));
let name_tok = node
.children_with_tokens()
.filter_map(|el| el.into_token())
.filter(|t| t.kind() == SyntaxKind::IDENT)
.last()
.or_else(|| {
node.children_with_tokens()
.filter_map(|el| el.into_token())
.filter(|t| t.kind() == SyntaxKind::UNDERSCORE)
.last()
})?;
Some(crate::ClosureParam {
name: name_tok.text().to_string(),
type_hint,
range: range_from_offsets(source, start, end),
})
}
fn lower_closure_v2(node: &SyntaxNode, source: &str) -> Option<Node> {
let r = node.text_range();
let start: usize = r.start().into();
let end: usize = r.end().into();
let mut params: Vec<crate::ClosureParam> = Vec::new();
let mut return_type: Option<crate::TypeNode> = None;
let mut body_ast: Option<ast::Expr> = None;
let mut saw_arrow = false;
for el in node.children_with_tokens() {
match el {
rowan::NodeOrToken::Token(t) => {
if t.kind() == SyntaxKind::THIN_ARROW {
saw_arrow = true;
}
}
rowan::NodeOrToken::Node(n) => match n.kind() {
SyntaxKind::CLOSURE_PARAM => {
params.push(lower_closure_param_v2(&n, source)?);
}
SyntaxKind::TYPE_NODE if saw_arrow && return_type.is_none() => {
return_type = Some(lower_type_node_from_cst(&n, source)?);
}
_ => {
if let Some(e) = ast::Expr::cast(n) {
if body_ast.is_none() {
body_ast = Some(e);
}
}
}
},
}
}
let body_ast = body_ast?;
let body = lower_expr_v2(&body_ast, source)?;
Some(Node::new(
Expr::Closure {
params,
return_type,
body,
},
range_from_offsets(source, start, end),
))
}
fn lower_call_expr_v2(node: &SyntaxNode, source: &str) -> Option<Node> {
let mut callee_node: Option<SyntaxNode> = None;
let mut call_arg_node: Option<SyntaxNode> = None;
for child in node.children() {
if child.kind() == SyntaxKind::CALL_ARG {
call_arg_node = Some(child);
} else if callee_node.is_none() && ast::Expr::cast(child.clone()).is_some() {
callee_node = Some(child);
}
}
let callee = callee_node?;
if callee.kind() != SyntaxKind::VARIABLE_EXPR {
return None;
}
let path = walk_path_tokens(&callee, source, false)?;
let callee_start: usize = callee.text_range().start().into();
let end: usize = call_arg_node
.as_ref()
.map(|n| n.text_range().end().into())
.unwrap_or_else(|| node.text_range().end().into());
let args = if let Some(args_node) = call_arg_node {
walk_call_arg_node(&args_node, source)?
} else {
Vec::new()
};
let mut saw_named = false;
for a in &args {
if a.name.is_some() {
saw_named = true;
} else if saw_named {
return None;
}
}
Some(Node::new(
Expr::FnCall { path, args },
range_from_offsets(source, callee_start, end),
))
}
fn lower_binary_expr_v2(node: &SyntaxNode, source: &str) -> Option<Node> {
let mut exprs = node.children().filter_map(ast::Expr::cast);
let lhs_ast = exprs.next()?;
let rhs_ast = exprs.next()?;
let op_token = node
.children_with_tokens()
.filter_map(|el| el.into_token())
.find(|t| {
matches!(
t.kind(),
SyntaxKind::PLUS
| SyntaxKind::MINUS
| SyntaxKind::STAR
| SyntaxKind::SLASH
| SyntaxKind::PERCENT
| SyntaxKind::PLUS_PLUS
| SyntaxKind::EQ_EQ
| SyntaxKind::BANG_EQ
| SyntaxKind::LT
| SyntaxKind::GT
| SyntaxKind::LT_EQ
| SyntaxKind::GT_EQ
| SyntaxKind::AMP_AMP
| SyntaxKind::PIPE_PIPE
| SyntaxKind::PIPE
)
})?;
let op = match op_token.kind() {
SyntaxKind::PLUS => crate::Operator::Add,
SyntaxKind::MINUS => crate::Operator::Sub,
SyntaxKind::STAR => crate::Operator::Mul,
SyntaxKind::SLASH => crate::Operator::Div,
SyntaxKind::PERCENT => crate::Operator::Mod,
SyntaxKind::PLUS_PLUS => crate::Operator::Concat,
SyntaxKind::EQ_EQ => crate::Operator::Eq,
SyntaxKind::BANG_EQ => crate::Operator::Ne,
SyntaxKind::LT => crate::Operator::Lt,
SyntaxKind::GT => crate::Operator::Gt,
SyntaxKind::LT_EQ => crate::Operator::Le,
SyntaxKind::GT_EQ => crate::Operator::Ge,
SyntaxKind::AMP_AMP => crate::Operator::And,
SyntaxKind::PIPE_PIPE => crate::Operator::Or,
SyntaxKind::PIPE => crate::Operator::Pipe,
_ => return None,
};
let lhs = lower_expr_v2(&lhs_ast, source)?;
let rhs = lower_expr_v2(&rhs_ast, source)?;
let combined = crate::combine_ranges(lhs.range, rhs.range);
Some(Node::new(Expr::Binary(op, lhs, rhs), combined))
}
fn lower_ternary_expr_v2(node: &SyntaxNode, source: &str) -> Option<Node> {
let r = node.text_range();
let start: usize = r.start().into();
let end: usize = r.end().into();
let mut exprs = node.children().filter_map(ast::Expr::cast);
let cond_ast = exprs.next()?;
let then_ast = exprs.next()?;
let els_ast = exprs.next()?;
let cond = lower_expr_v2(&cond_ast, source)?;
let then = lower_expr_v2(&then_ast, source)?;
let els = lower_expr_v2(&els_ast, source)?;
Some(Node::new(
Expr::Ternary { cond, then, els },
range_from_offsets(source, start, end),
))
}
fn lower_where_expr_v2(node: &SyntaxNode, source: &str) -> Option<Node> {
let r = node.text_range();
let start: usize = r.start().into();
let end: usize = r.end().into();
let mut exprs = node.children().filter_map(ast::Expr::cast);
let base_ast = exprs.next()?;
let bindings_ast = exprs.next()?;
let base = lower_expr_v2(&base_ast, source)?;
let bindings = lower_expr_v2(&bindings_ast, source)?;
Some(Node::new(
Expr::Where {
expr: base,
bindings,
},
range_from_offsets(source, start, end),
))
}
fn lower_match_expr_v2(node: &SyntaxNode, source: &str) -> Option<Node> {
let r = node.text_range();
let start: usize = r.start().into();
let end: usize = r.end().into();
let scrutinee_ast = node.children().find_map(ast::Expr::cast)?;
let scrutinee = lower_expr_v2(&scrutinee_ast, source)?;
let mut arms: Vec<(Node, Node)> = Vec::new();
for arm in node
.children()
.filter(|c| c.kind() == SyntaxKind::MATCH_ARM)
{
let mut pattern: Option<Node> = None;
let mut body: Option<Node> = None;
for child in arm.children() {
match child.kind() {
SyntaxKind::MATCH_PATTERN => {
pattern = Some(lower_match_pattern_v2(&child, source)?);
}
SyntaxKind::WILDCARD if pattern.is_none() => {
let pat_ast = ast::Expr::cast(child)?;
pattern = Some(lower_expr_v2(&pat_ast, source)?);
}
_ => {
if let Some(expr_ast) = ast::Expr::cast(child) {
let lowered = lower_expr_v2(&expr_ast, source)?;
if pattern.is_none() {
pattern = Some(lowered);
} else if body.is_none() {
body = Some(lowered);
}
}
}
}
}
arms.push((pattern?, body?));
}
Some(Node::new(
Expr::Match {
expr: scrutinee,
arms,
},
range_from_offsets(source, start, end),
))
}
fn lower_match_pattern_v2(node: &SyntaxNode, source: &str) -> Option<Node> {
let r = node.text_range();
let start: usize = r.start().into();
let end: usize = r.end().into();
let mut tokens = node
.children_with_tokens()
.filter_map(|el| el.into_token())
.filter(|tok| !tok.kind().is_trivia())
.peekable();
let mut path: Vec<String> = Vec::new();
let first = tokens.next()?;
if first.kind() != SyntaxKind::IDENT {
return None;
}
path.push(first.text().to_string());
while matches!(tokens.peek().map(|t| t.kind()), Some(SyntaxKind::DOT)) {
tokens.next();
let ident = tokens.next()?;
if ident.kind() != SyntaxKind::IDENT {
return None;
}
path.push(ident.text().to_string());
}
let variant = path.pop()?;
let enum_path = path;
let mut bindings = Vec::new();
match tokens.peek().map(|t| t.kind()) {
Some(SyntaxKind::L_PAREN) => {
tokens.next();
for tok in tokens.by_ref() {
match tok.kind() {
SyntaxKind::R_PAREN => break,
SyntaxKind::COMMA => continue,
SyntaxKind::UNDERSCORE => bindings.push(PatternBinding {
field: None,
binding: None,
}),
SyntaxKind::IDENT => bindings.push(PatternBinding {
field: None,
binding: Some(tok.text().to_string()),
}),
_ => return None,
}
}
}
Some(SyntaxKind::L_BRACE) => {
tokens.next();
while let Some(tok) = tokens.next() {
match tok.kind() {
SyntaxKind::R_BRACE => break,
SyntaxKind::COMMA => continue,
SyntaxKind::IDENT => {
let field = tok.text().to_string();
let binding =
if matches!(tokens.peek().map(|t| t.kind()), Some(SyntaxKind::COLON)) {
tokens.next();
let bind_tok = tokens.next()?;
match bind_tok.kind() {
SyntaxKind::UNDERSCORE => None,
SyntaxKind::IDENT => Some(bind_tok.text().to_string()),
_ => return None,
}
} else {
Some(field.clone())
};
bindings.push(PatternBinding {
field: Some(field),
binding,
});
}
_ => return None,
}
}
}
_ => {}
}
Some(Node::new(
Expr::VariantPattern {
enum_path,
variant,
bindings,
},
range_from_offsets(source, start, end),
))
}
#[allow(dead_code)]
pub(crate) fn lower_expr_v2(expr: &ast::Expr, source: &str) -> Option<Node> {
match expr {
ast::Expr::Literal(lit) => lower_atom_via_legacy(lit.syntax(), source),
ast::Expr::Variable(v) => lower_atom_via_legacy(v.syntax(), source),
ast::Expr::Reference(r) => lower_atom_via_legacy(r.syntax(), source),
ast::Expr::Wildcard(w) => lower_atom_via_legacy(w.syntax(), source),
ast::Expr::Dict(d) => lower_dict_v2(d.syntax(), source),
ast::Expr::List(l) => lower_list_v2(l.syntax(), source),
ast::Expr::Tuple(t) => lower_tuple_v2(t.syntax(), source),
ast::Expr::Spread(s) => lower_spread_expr_v2(s.syntax(), source),
ast::Expr::Comprehension(c) => lower_comprehension_v2(c.syntax(), source),
ast::Expr::Binary(b) => lower_binary_expr_v2(b.syntax(), source),
ast::Expr::Unary(u) => lower_unary_expr_v2(u.syntax(), source),
ast::Expr::Ternary(t) => lower_ternary_expr_v2(t.syntax(), source),
ast::Expr::Call(c) => lower_call_expr_v2(c.syntax(), source),
ast::Expr::Closure(c) => lower_closure_v2(c.syntax(), source),
ast::Expr::Match(m) => lower_match_expr_v2(m.syntax(), source),
ast::Expr::Where(w) => lower_where_expr_v2(w.syntax(), source),
ast::Expr::VariantCtor(v) => lower_variant_ctor_v2(v.syntax(), source),
ast::Expr::FString(fs) => lower_fstring_v2(fs.syntax(), source),
ast::Expr::Type(t) => lower_type_expr_v2(t.syntax(), source),
ast::Expr::Error(_) => None,
}
}
#[allow(dead_code)]
pub fn lower_document_node_v2(doc: &ast::Document, source: &str) -> Option<Node> {
let mut decorators: Vec<crate::Decorator> = Vec::new();
for d in doc.decorators() {
match lower_decorator_v2(&d, source) {
Some(dec) => decorators.push(dec),
None if is_recovering() => continue,
None => return None,
}
}
let mut directives: Vec<crate::Directive> = Vec::new();
for d in doc.directives() {
match lower_directive_v2(&d, source) {
Some(dir) => directives.push(dir),
None if is_recovering() => continue,
None => return None,
}
}
let root_ast = doc.root_expr()?;
let mut body = match lower_expr_v2(&root_ast, source) {
Some(n) => n,
None if is_recovering() => {
let r = root_ast.syntax().text_range();
let start_o: usize = r.start().into();
let end_o: usize = r.end().into();
let placeholder_expr = match root_ast.syntax().kind() {
SyntaxKind::DICT => Expr::Dict(Vec::new()),
SyntaxKind::LIST => Expr::List(Vec::new()),
_ => Expr::Missing,
};
Node::new(placeholder_expr, range_from_offsets(source, start_o, end_o))
}
None => return None,
};
let start_offset = decorators
.first()
.map(|d| d.range.start.offset)
.or_else(|| directives.first().map(|d| d.range.start.offset))
.unwrap_or(body.range.start.offset);
if matches!(body.expr.as_ref(), Expr::Dict(_)) {
directives.extend(std::mem::take(&mut body.directives));
}
let leading_slice = source.get(0..start_offset).unwrap_or("");
let (doc_comment, _) = crate::parse_leading_comments(leading_slice);
let end_offset = body.range.end.offset;
let range = range_from_offsets(source, start_offset, end_offset);
Some(Node {
id: crate::NodeId::alloc(),
expr: body.expr,
decorators,
directives,
type_hint: None,
range,
doc_comment,
})
}
#[allow(dead_code)]
pub(crate) fn lower_document_v2(source: &str) -> Option<Node> {
let parse = crate::cst::parse_cst(source);
let doc = ast::document_of(parse.syntax())?;
lower_document_node_v2(&doc, source)
}
#[allow(dead_code)]
pub(crate) fn first_real_error(parse: &Parse) -> Option<&crate::cst::ParseError> {
parse.errors.first()
}
pub fn lower_document(parse: &Parse, source: &str) -> Result<crate::Node, ParseDocumentError> {
if !parse.has_errors() {
if let Some(doc) = ast::document_of(parse.syntax()) {
if doc.root_expr().is_none() {
return Err(ParseDocumentError::Parse {
offset: 0,
message: "empty document".to_string(),
});
}
if let Some(node) = lower_document_node_v2(&doc, source) {
return Ok(node);
}
return Err(ParseDocumentError::Parse {
offset: 0,
message: "could not lower well-formed CST to legacy Node".to_string(),
});
}
return Err(ParseDocumentError::Parse {
offset: 0,
message: "empty document".to_string(),
});
}
let err = parse
.errors
.first()
.expect("parse.has_errors() implies at least one error");
if err.message.starts_with("trailing input after root value") {
let mut start = err.offset;
start += trim_leading_trivia(source.get(start..).unwrap_or(""));
let remaining: String = source.get(start..).unwrap_or("").chars().take(64).collect();
return Err(ParseDocumentError::TrailingInput {
offset: start,
remaining,
});
}
Err(ParseDocumentError::Parse {
offset: err.offset,
message: err.message.clone(),
})
}
#[allow(dead_code)]
fn has_error_descendant(node: &SyntaxNode) -> bool {
node.descendants().any(|n| n.kind() == SyntaxKind::ERROR)
}
#[allow(dead_code)]
fn first_error_offset(node: &SyntaxNode) -> Option<usize> {
node.descendants()
.find(|n| n.kind() == SyntaxKind::ERROR)
.map(|n| usize::from(n.text_range().start()))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{cst, parse_document, NodeId};
#[allow(dead_code)]
fn strip_node_ids(node: &mut crate::Node) {
node.id = NodeId::SYNTHETIC;
match std::sync::Arc::make_mut(&mut node.expr) {
Expr::Dict(pairs) => {
for (key, value) in pairs {
if let crate::TokenKey::Dynamic(inner, _) = key {
strip_node_ids(inner);
}
strip_node_ids(value);
}
}
Expr::List(items) | Expr::Tuple(items) => {
for item in items {
strip_node_ids(item);
}
}
Expr::Spread(inner) => strip_node_ids(inner),
Expr::Comprehension {
element,
iterable,
condition,
..
} => {
strip_node_ids(element);
strip_node_ids(iterable);
if let Some(c) = condition {
strip_node_ids(c);
}
}
Expr::Variable(path) | Expr::Reference { path, .. } => {
for tk in path {
if let crate::TokenKey::Dynamic(inner, _) = tk {
strip_node_ids(inner);
}
}
}
Expr::Binary(_, l, r) => {
strip_node_ids(l);
strip_node_ids(r);
}
Expr::Unary(_, inner) => strip_node_ids(inner),
Expr::Ternary { cond, then, els } => {
strip_node_ids(cond);
strip_node_ids(then);
strip_node_ids(els);
}
Expr::FnCall { path, args } => {
for tk in path {
if let crate::TokenKey::Dynamic(inner, _) = tk {
strip_node_ids(inner);
}
}
for arg in args {
strip_node_ids(&mut arg.value);
}
}
Expr::FString(parts) => {
for part in parts {
if let crate::FStringPart::Interpolation(n) = part {
strip_node_ids(n);
}
}
}
Expr::Where { expr, bindings } => {
strip_node_ids(expr);
strip_node_ids(bindings);
}
Expr::Match { expr, arms } => {
strip_node_ids(expr);
for (pat, body) in arms {
strip_node_ids(pat);
strip_node_ids(body);
}
}
Expr::Closure { body, .. } => strip_node_ids(body),
Expr::VariantCtor { body, .. } => strip_node_ids(body),
Expr::Missing
| Expr::Bool(_)
| Expr::Int(_)
| Expr::Float(_)
| Expr::String(_)
| Expr::Type(_)
| Expr::Wildcard
| Expr::VariantPattern { .. } => {}
}
for dec in &mut node.decorators {
for arg in &mut dec.args {
strip_node_ids(&mut arg.value);
}
}
for dir in &mut node.directives {
match &mut dir.body {
crate::DirectiveBody::Value(n) => strip_node_ids(n),
crate::DirectiveBody::NameBody { body, methods, .. } => {
strip_node_ids(body);
for m in methods {
if let Some(b) = &mut m.body {
strip_node_ids(b);
}
}
}
crate::DirectiveBody::Bare
| crate::DirectiveBody::Import { .. }
| crate::DirectiveBody::Main { .. } => {}
}
}
}
#[test]
fn lowering_detects_cst_error_descendant() {
let parse = cst::parse_cst("{ broken @ # }");
assert!(parse.has_errors() || has_error_descendant(&parse.syntax()));
assert!(first_error_offset(&parse.syntax()).is_some() || parse.has_errors());
}
#[test]
fn corpus_lowering_succeeds() {
use std::fs;
use std::path::PathBuf;
let crate_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let workspace_root = crate_dir
.parent()
.and_then(|p| p.parent())
.expect("workspace root")
.to_path_buf();
let mut files = Vec::new();
walk(&workspace_root, &mut files);
files.retain(|p| !p.to_string_lossy().contains("/target/"));
let mut checked = 0usize;
for path in files {
let Ok(source) = fs::read_to_string(&path) else {
continue;
};
if source.is_empty() {
continue;
}
if path.to_string_lossy().contains("/fixtures/broken/") {
continue;
}
if let Ok(mut node) = parse_document(&source) {
strip_node_ids(&mut node);
checked += 1;
}
}
assert!(checked > 0, "expected to lower at least one fixture");
}
fn walk(dir: &std::path::Path, out: &mut Vec<std::path::PathBuf>) {
let Ok(read) = std::fs::read_dir(dir) else {
return;
};
for entry in read.flatten() {
let p = entry.path();
if p.is_dir() {
let name = p.file_name().and_then(|n| n.to_str()).unwrap_or("");
if matches!(name, "target" | "node_modules" | ".git") {
continue;
}
walk(&p, out);
} else if p.extension().and_then(|e| e.to_str()) == Some("relon") {
out.push(p);
}
}
}
#[test]
fn trailing_input_uses_legacy_offset() {
let err = parse_document("{ a: 1 } true").unwrap_err();
assert!(matches!(
err,
ParseDocumentError::TrailingInput { offset: 9, ref remaining }
if remaining == "true"
));
}
#[test]
fn schema_colon_body_form_parses() {
let src = r#"{
#schema Image: { name: String },
data: { name: "img" }
}"#;
let parse = cst::parse_cst(src);
let node = lower_document(&parse, src).expect("schema-colon shape lowers cleanly");
assert!(matches!(*node.expr, Expr::Dict(_)));
assert!(!parse.has_errors(), "no CST errors: {:?}", parse.errors);
}
#[test]
fn parse_import_with_sha256_integrity() {
let src = "#import lib from \"./lib.relon\" sha256:\"abcd1234\"\n{ x: 1 }";
let node = parse_document(src).expect("parse #import with sha256");
let dir = node
.directives
.iter()
.find(|d| d.name == "import")
.expect("import directive present");
match &dir.body {
crate::DirectiveBody::Import {
path,
integrity: Some(int),
..
} => {
assert_eq!(path, "./lib.relon");
assert_eq!(int.algorithm, Some(crate::HashAlgorithm::Sha256));
assert_eq!(int.algorithm_text, "sha256");
assert_eq!(int.hex, "abcd1234");
assert!(int.range.start.offset > 0);
assert!(int.range.end.offset > int.range.start.offset);
}
other => panic!("unexpected import body shape: {other:?}"),
}
}
#[test]
fn parse_import_without_hash_is_unpinned() {
let src = "#import lib from \"./lib.relon\"\n{ x: 1 }";
let node = parse_document(src).expect("parse plain #import");
let dir = node
.directives
.iter()
.find(|d| d.name == "import")
.expect("import directive present");
match &dir.body {
crate::DirectiveBody::Import { integrity, .. } => {
assert!(integrity.is_none(), "expected no integrity pin");
}
other => panic!("unexpected import body shape: {other:?}"),
}
}
#[test]
fn parse_import_integrity_keeps_unknown_algorithm_in_ast() {
let src = "#import lib from \"./lib.relon\" sha512:\"deadbeef\"\n{ x: 1 }";
let node = parse_document(src).expect("parser accepts unknown algorithm");
let dir = node
.directives
.iter()
.find(|d| d.name == "import")
.expect("import directive present");
match &dir.body {
crate::DirectiveBody::Import {
integrity: Some(int),
..
} => {
assert_eq!(int.algorithm, None);
assert_eq!(int.algorithm_text, "sha512");
}
other => panic!("expected integrity pin to be parsed, got {other:?}"),
}
}
#[test]
fn parse_document_accepts_legacy_corpus_samples() {
for src in [
"{ x: 1 }",
"[1, 2, 3]",
"42",
"true",
"1 + 2",
r#""hello""#,
"range(0, 10)",
"Result.Ok { value: 1 }",
"{ a: 1 } // trailing\n /* ok */",
] {
parse_document(src)
.unwrap_or_else(|e| panic!("parse_document failed on {src:?}: {e:?}"));
}
}
}