Skip to content

Commit 0436698

Browse files
committed
feat!(parser): type inferrence following cellpath
1 parent 395ef5b commit 0436698

4 files changed

Lines changed: 83 additions & 35 deletions

File tree

crates/nu-cli/src/completions/cell_path_completions.rs

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@ impl Completer for CellPathCompletion<'_> {
7272
for suggestion in get_suggestions_by_value(&value, current_span) {
7373
matcher.add_semantic_suggestion(suggestion);
7474
}
75-
} else if let Some(ty) = type_follow_cell_path(&self.full_cell_path.head.ty, path_members) {
76-
for suggestion in get_suggestions_by_type(ty, current_span) {
75+
} else if let Some(ty) = self.full_cell_path.head.ty.follow_cell_path(path_members) {
76+
for suggestion in get_suggestions_by_type(&ty, current_span) {
7777
matcher.add_semantic_suggestion(suggestion);
7878
}
7979
}
@@ -155,32 +155,6 @@ fn get_suggestions_by_value(
155155
}
156156
}
157157

158-
fn type_follow_cell_path<'a>(
159-
parent_type: &'a Type,
160-
path_members: &[PathMember],
161-
) -> Option<&'a Type> {
162-
match (parent_type, path_members.first()) {
163-
(Type::Table(fields), Some(PathMember::String { val, .. }))
164-
| (Type::Record(fields), Some(PathMember::String { val, .. })) => {
165-
let sub_type = fields
166-
.iter()
167-
.find_map(|(name, ty)| (name == val).then_some(ty))?;
168-
type_follow_cell_path(sub_type, &path_members[1..])
169-
}
170-
(Type::Table(_), Some(PathMember::Int { .. })) => {
171-
type_follow_cell_path(parent_type, &path_members[1..])
172-
}
173-
(Type::List(inner), Some(PathMember::Int { .. })) => {
174-
type_follow_cell_path(inner, &path_members[1..])
175-
}
176-
(Type::List(inner), Some(PathMember::String { .. })) => {
177-
type_follow_cell_path(inner, path_members)
178-
}
179-
(_, None) => Some(parent_type),
180-
_ => None,
181-
}
182-
}
183-
184158
fn get_suggestions_by_type(ty: &Type, current_span: reedline::Span) -> Vec<SemanticSuggestion> {
185159
match ty {
186160
Type::Record(fields) | Type::Table(fields) => fields

crates/nu-cli/src/completions/operator_completions.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ impl Completer for OperatorCompletion<'_> {
228228
Type::Record(_) | Type::Range => collection_comparison_ops(),
229229
Type::List(_) | Type::Table(_) => valid_list_ops(),
230230
// Unknown type, resort to evaluated values
231-
Type::Any => match &self.left_hand_side.expr {
231+
Type::Any | Type::OneOf(_) => match &self.left_hand_side.expr {
232232
Expr::FullCellPath(path) => {
233233
// for `$ <tab>`
234234
if let Expr::Garbage = path.head.expr {

crates/nu-parser/src/parser.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2774,11 +2774,11 @@ pub fn parse_full_cell_path(
27742774
};
27752775

27762776
let tail = parse_cell_path(working_set, tokens, expect_dot);
2777-
// FIXME: Get the type of the data at the tail using follow_cell_path() (or something)
27782777
let ty = if !tail.is_empty() {
2779-
// Until the aforementioned fix is implemented, this is necessary to allow mutable list upserts
2780-
// such as $a.1 = 2 to work correctly.
2781-
Type::Any
2778+
head.ty
2779+
.follow_cell_path(&tail)
2780+
.map(|ty| ty.into_owned())
2781+
.unwrap_or(Type::Any)
27822782
} else {
27832783
head.ty.clone()
27842784
};

crates/nu-protocol/src/ty.rs

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
use crate::SyntaxShape;
1+
use crate::{SyntaxShape, ast::PathMember};
22
use serde::{Deserialize, Serialize};
3-
use std::fmt::Display;
3+
use std::{borrow::Cow, fmt::Display};
44
#[cfg(test)]
55
use strum_macros::EnumIter;
66

@@ -35,6 +35,76 @@ pub enum Type {
3535
Table(Box<[(String, Type)]>),
3636
}
3737

38+
fn follow_cell_path_recursive<'a>(
39+
current: Cow<'a, Type>,
40+
path_members: &mut dyn Iterator<Item = &'a PathMember>,
41+
) -> Option<Cow<'a, Type>> {
42+
let Some(first) = path_members.next() else {
43+
return Some(current);
44+
};
45+
match (current.as_ref(), first) {
46+
(Type::Record(fields), PathMember::String { val, .. }) => {
47+
let idx = fields.iter().position(|(name, _)| name == val)?;
48+
let next = match current {
49+
Cow::Borrowed(Type::Record(f)) => Cow::Borrowed(&f[idx].1),
50+
Cow::Owned(Type::Record(f)) => Cow::Owned(f[idx].1.to_owned()),
51+
_ => unreachable!(),
52+
};
53+
follow_cell_path_recursive(next, path_members)
54+
}
55+
56+
// Table to Record (Int)
57+
(Type::Table(f), PathMember::Int { .. }) => {
58+
follow_cell_path_recursive(Cow::Owned(Type::Record(f.clone())), path_members)
59+
}
60+
61+
// Table to List (String)
62+
(Type::Table(fields), PathMember::String { val, .. }) => {
63+
let (_, sub_type) = fields.iter().find(|(name, _)| name == val)?;
64+
let list_type = Type::List(Box::new(sub_type.clone()));
65+
follow_cell_path_recursive(Cow::Owned(list_type), path_members)
66+
}
67+
68+
(Type::List(_), PathMember::Int { .. }) => {
69+
let next = match current {
70+
Cow::Borrowed(Type::List(i)) => Cow::Borrowed(i.as_ref()),
71+
Cow::Owned(Type::List(i)) => Cow::Owned(*i),
72+
_ => unreachable!(),
73+
};
74+
follow_cell_path_recursive(next, path_members)
75+
}
76+
77+
// List of Records indexed by key names
78+
(Type::List(_), PathMember::String { .. }) => {
79+
let next = match current {
80+
Cow::Borrowed(Type::List(i)) => Cow::Borrowed(i.as_ref()),
81+
Cow::Owned(Type::List(i)) => Cow::Owned(*i),
82+
_ => unreachable!(),
83+
};
84+
85+
let mut found_int_member = false;
86+
let mut new_iter = std::iter::once(first).chain(path_members).filter(|pm| {
87+
let first_int = !found_int_member && matches!(pm, PathMember::Int { .. });
88+
if first_int {
89+
found_int_member = true;
90+
}
91+
!first_int
92+
});
93+
let inner_ty = follow_cell_path_recursive(next, &mut new_iter);
94+
95+
// If there's no int path member, need to wrap in a List type
96+
// e.g. [{foo: bar}].foo -> [bar], list<record<foo: string>> -> list<string>
97+
if found_int_member {
98+
inner_ty
99+
} else {
100+
inner_ty.map(|inner_ty| Cow::Owned(Type::List(Box::new(inner_ty.into_owned()))))
101+
}
102+
}
103+
104+
_ => None,
105+
}
106+
}
107+
38108
impl Type {
39109
pub fn list(inner: Type) -> Self {
40110
Self::List(Box::new(inner))
@@ -294,6 +364,10 @@ impl Type {
294364
Type::Glob => String::from("glob"),
295365
}
296366
}
367+
368+
pub fn follow_cell_path<'a>(&'a self, path_members: &'a [PathMember]) -> Option<Cow<'a, Self>> {
369+
follow_cell_path_recursive(Cow::Borrowed(self), &mut path_members.iter())
370+
}
297371
}
298372

299373
impl Display for Type {

0 commit comments

Comments
 (0)