Support widgets and placeholders in more complex expressions (#5656)

Implements https://github.com/enso-org/enso/issues/5032

Added support for widgets in infix expressions (right now only used for file paths)
<img width="306" alt="image" src="https://user-images.githubusercontent.com/919491/218736181-98965cd4-2a8e-4f7a-bbf8-ab302ac5b22c.png">

Widgets and placeholders are handled for all chained methods within a node
<img width="292" alt="image" src="https://user-images.githubusercontent.com/919491/218736249-a0190115-623e-4e66-aff4-90eb2a50685d.png">

The qualified method call convention and static methods no longer confuse the argument placeholders
<img width="374" alt="image" src="https://user-images.githubusercontent.com/919491/218736628-24073f5c-0512-4b37-a271-0248bc954802.png">

Type constructor expressions now receive placeholder arguments. The placeholders work on nested expressions within a node.
<img width="405" alt="image" src="https://user-images.githubusercontent.com/919491/218737379-b53ff125-3910-48f3-bb9f-a0e7b1684ab9.png">
This commit is contained in:
Paweł Grabarz 2023-02-20 07:39:39 +01:00 committed by GitHub
parent 663ed1e07e
commit ccdfaca584
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 302 additions and 197 deletions

View File

@ -95,6 +95,9 @@
- [Improved component browser entry filtering and sorting][5645]. The component - [Improved component browser entry filtering and sorting][5645]. The component
browser will now provide suggestions matching either the component's label or browser will now provide suggestions matching either the component's label or
the corresponding code. the corresponding code.
- [Improved argument placeholder resolution in more complex expressions][5656].
It is now possible to drop node connections onto missing arguments of chained
and nested function calls.
#### EnsoGL (rendering engine) #### EnsoGL (rendering engine)
@ -480,6 +483,7 @@
[4072]: https://github.com/enso-org/enso/pull/4072 [4072]: https://github.com/enso-org/enso/pull/4072
[5645]: https://github.com/enso-org/enso/pull/5645 [5645]: https://github.com/enso-org/enso/pull/5645
[5646]: https://github.com/enso-org/enso/pull/5646 [5646]: https://github.com/enso-org/enso/pull/5646
[5656]: https://github.com/enso-org/enso/pull/5656
#### Enso Compiler #### Enso Compiler

View File

@ -294,9 +294,19 @@ impl GeneralizedInfix {
infix_id: self.id, infix_id: self.id,
}; };
let rest_offset = rest.operand.as_ref().map_or_default(|op| op.offset);
let target_subtree_infix = target.clone().and_then(|arg| { let target_subtree_infix = target.clone().and_then(|arg| {
let offset = arg.offset; let offset = arg.offset;
GeneralizedInfix::try_new(&arg.arg).map(|arg| ArgWithOffset { arg, offset }) GeneralizedInfix::try_new(&arg.arg).map(|arg| ArgWithOffset { arg, offset }).filter(
|target_infix| {
// For access operators, do not flatten them if there is a space before the dot.
// For example, `Foo . Bar . Baz` should not be flattened to `Foo.Bar.Baz`, as
// those should be treated as potential separate prefix expressions, allowing
// operator placeholders to be inserted.
rest_offset == 0 || target_infix.arg.name() != predefined::ACCESS
},
)
}); });
let mut target_subtree_flat = match target_subtree_infix { let mut target_subtree_flat = match target_subtree_infix {
Some(target_infix) if target_infix.arg.name() == self.name() => Some(target_infix) if target_infix.arg.name() == self.name() =>

View File

@ -26,8 +26,8 @@ pub fn main() {
let parens_cr1 = ast::crumbs::MatchCrumb::Segs { val: val.clone(), index: 0 }; let parens_cr1 = ast::crumbs::MatchCrumb::Segs { val: val.clone(), index: 0 };
let parens_cr = ast::crumbs::MatchCrumb::Segs { val, index: 0 }; let parens_cr = ast::crumbs::MatchCrumb::Segs { val, index: 0 };
let _input_span_tree = builder::TreeBuilder::<()>::new(36) let _input_span_tree = builder::TreeBuilder::<()>::new(36)
.add_child(0, 14, node::Kind::Chained, PrefixCrumb::Func) .add_child(0, 14, node::Kind::chained(), PrefixCrumb::Func)
.add_child(0, 9, node::Kind::operation(), PrefixCrumb::Func) .add_child(0, 9, node::Kind::Operation, PrefixCrumb::Func)
.set_ast_id(Uuid::new_v4()) .set_ast_id(Uuid::new_v4())
.done() .done()
.add_empty_child(10, InsertionPointType::BeforeTarget) .add_empty_child(10, InsertionPointType::BeforeTarget)
@ -41,7 +41,7 @@ pub fn main() {
.set_ast_id(Uuid::new_v4()) .set_ast_id(Uuid::new_v4())
.add_child(1, 19, node::Kind::argument(), parens_cr1) .add_child(1, 19, node::Kind::argument(), parens_cr1)
.set_ast_id(Uuid::new_v4()) .set_ast_id(Uuid::new_v4())
.add_child(0, 12, node::Kind::operation(), PrefixCrumb::Func) .add_child(0, 12, node::Kind::Operation, PrefixCrumb::Func)
.set_ast_id(Uuid::new_v4()) .set_ast_id(Uuid::new_v4())
.done() .done()
.add_empty_child(13, InsertionPointType::BeforeTarget) .add_empty_child(13, InsertionPointType::BeforeTarget)
@ -57,11 +57,11 @@ pub fn main() {
let input_span_tree2 = Node::<()>::new() let input_span_tree2 = Node::<()>::new()
.new_child(|t| { .new_child(|t| {
t.new_ast_id() t.new_ast_id()
.kind(node::Kind::Chained) .kind(node::Kind::chained())
.crumbs(PrefixCrumb::Func) .crumbs(PrefixCrumb::Func)
.new_child(|t| { .new_child(|t| {
t.size(9.bytes()) t.size(9.bytes())
.kind(node::Kind::operation()) .kind(node::Kind::Operation)
.crumbs(PrefixCrumb::Func) .crumbs(PrefixCrumb::Func)
.new_ast_id() .new_ast_id()
}) })
@ -85,7 +85,7 @@ pub fn main() {
.crumbs(parens_cr) .crumbs(parens_cr)
.new_child(|t| { .new_child(|t| {
t.size(12.bytes()) t.size(12.bytes())
.kind(node::Kind::operation()) .kind(node::Kind::Operation)
.crumbs(PrefixCrumb::Func) .crumbs(PrefixCrumb::Func)
.new_ast_id() .new_ast_id()
}) })

View File

@ -390,7 +390,7 @@ mod test {
// Consider Span Tree for `foo bar` where `foo` is a method known to take 3 parameters. // Consider Span Tree for `foo bar` where `foo` is a method known to take 3 parameters.
// We can try setting each of 3 arguments to `baz`. // We can try setting each of 3 arguments to `baz`.
let tree = TreeBuilder::<()>::new(7) let tree = TreeBuilder::<()>::new(7)
.add_leaf(0, 3, node::Kind::operation(), PrefixCrumb::Func) .add_leaf(0, 3, node::Kind::Operation, PrefixCrumb::Func)
.add_leaf(4, 7, node::Kind::this(), PrefixCrumb::Arg) .add_leaf(4, 7, node::Kind::this(), PrefixCrumb::Arg)
.add_empty_child(7, ExpectedArgument(1)) .add_empty_child(7, ExpectedArgument(1))
.add_empty_child(7, ExpectedArgument(2)) .add_empty_child(7, ExpectedArgument(2))
@ -416,7 +416,7 @@ mod test {
// parameters. We can try setting each of 2 arguments to `baz`. // parameters. We can try setting each of 2 arguments to `baz`.
let tree: SpanTree = TreeBuilder::new(10) let tree: SpanTree = TreeBuilder::new(10)
.add_leaf(0, 4, node::Kind::this(), InfixCrumb::LeftOperand) .add_leaf(0, 4, node::Kind::this(), InfixCrumb::LeftOperand)
.add_leaf(5, 6, node::Kind::operation(), InfixCrumb::Operator) .add_leaf(5, 6, node::Kind::Operation, InfixCrumb::Operator)
.add_leaf(7, 10, node::Kind::argument(), InfixCrumb::RightOperand) .add_leaf(7, 10, node::Kind::argument(), InfixCrumb::RightOperand)
.add_empty_child(10, ExpectedArgument(0)) .add_empty_child(10, ExpectedArgument(0))
.add_empty_child(10, ExpectedArgument(1)) .add_empty_child(10, ExpectedArgument(1))

View File

@ -193,10 +193,10 @@ impl<'a> ApplicationBase<'a> {
ApplicationBase { function_name, call_id, uses_method_notation: false } ApplicationBase { function_name, call_id, uses_method_notation: false }
} }
/// Get the list of method prefix arguments expected by this application. Does not include /// Get the list of method arguments expected by this application. Does not include `self`
/// `self` parameter when using method notation. Returns `None` when the call info is not /// parameter when using method notation. Returns `None` when the call info is not present in
/// present in the context or AST doesn't contain enough information to query it. /// the context or AST doesn't contain enough information to query it.
fn known_prefix_arguments(&self, context: &impl Context) -> Option<VecDeque<ArgumentInfo>> { fn known_arguments(&self, context: &impl Context) -> Option<VecDeque<ArgumentInfo>> {
// If method notation is used, the function name is required to get relevant call info. Do // If method notation is used, the function name is required to get relevant call info. Do
// not attempt the call if method name is not available, as the returned data would be not // not attempt the call if method name is not available, as the returned data would be not
// relevant. // relevant.
@ -205,17 +205,28 @@ impl<'a> ApplicationBase<'a> {
} }
let invocation_info = context.call_info(self.call_id?, self.function_name.as_deref())?; let invocation_info = context.call_info(self.call_id?, self.function_name.as_deref())?;
let self_in_access = !invocation_info.is_constructor
&& (!invocation_info.called_on_type.unwrap_or(false) || invocation_info.is_static);
let parameters = invocation_info.with_call_id(self.call_id).parameters; let parameters = invocation_info.with_call_id(self.call_id).parameters;
let mut deque: VecDeque<ArgumentInfo> = parameters.into(); let mut deque: VecDeque<ArgumentInfo> = parameters.into();
let first_argument_name = deque.front().and_then(|arg| arg.name.as_ref());
let has_self_argument = first_argument_name.map_or(false, |name| name == node::This::NAME);
// When a method notation is used, the first received argument is the target. Remove it from // When a method notation is used on non-static invocation, the first `self` argument is
// the list of expected prefix arguments. // already present the access chain. Remove it from the list of expected prefix arguments.
if self.uses_method_notation { if self.uses_method_notation && has_self_argument && self_in_access {
deque.pop_front(); deque.pop_front();
} }
Some(deque) Some(deque)
} }
/// Switch the method application to use different expression as call id, but keep the same
/// function name and notation.
fn set_call_id(&mut self, new_call_id: Option<Id>) {
self.call_id = new_call_id;
}
fn into_owned(self) -> ApplicationBase<'static> { fn into_owned(self) -> ApplicationBase<'static> {
let function_name = self.function_name.map(|s| s.into_owned().into()); let function_name = self.function_name.map(|s| s.into_owned().into());
ApplicationBase { function_name, ..self } ApplicationBase { function_name, ..self }
@ -241,30 +252,7 @@ fn generate_node_for_ast<T: Payload>(
context: &impl Context, context: &impl Context,
) -> FallibleResult<Node<T>> { ) -> FallibleResult<Node<T>> {
if let Some(infix) = GeneralizedInfix::try_new(ast) { if let Some(infix) = GeneralizedInfix::try_new(ast) {
// Code like `ast.func` or `a+b+c`. infix.generate_node(kind, context)
let app_base = ApplicationBase::from_infix(&infix);
let chain = infix.flatten();
if app_base.uses_method_notation {
// For method call, this is behaving like a prefix with single member. All prefix params
// are missing arguments, since there is no prefix application.
let missing_args = app_base.known_prefix_arguments(context).unwrap_or_default();
let arity = missing_args.len();
let base_node_kind = if arity == 0 {
kind.clone()
} else {
node::Kind::operation().with_call_id(app_base.call_id).into()
};
let node = chain.generate_node(base_node_kind, context)?;
let provided_prefix_arg_count = 0;
let args_iter = missing_args.into_iter();
Ok(generate_expected_arguments(node, kind, provided_prefix_arg_count, args_iter))
} else {
// For non-access infix operators, missing arguments are not handled at this level.
chain.generate_node(kind, context)
}
} else { } else {
match ast.shape() { match ast.shape() {
ast::Shape::Prefix(_) => ast::Shape::Prefix(_) =>
@ -274,13 +262,11 @@ fn generate_node_for_ast<T: Payload>(
_ => { _ => {
let size = (ast.len().value as i32).byte_diff(); let size = (ast.len().value as i32).byte_diff();
let ast_id = ast.id; let ast_id = ast.id;
let children = default();
let name = ast::identifier::name(ast); let name = ast::identifier::name(ast);
let payload = default();
if let Some(info) = ast.id.and_then(|id| context.call_info(id, name)) { if let Some(info) = ast.id.and_then(|id| context.call_info(id, name)) {
let node = { let node = {
let kind = node::Kind::operation().with_call_id(ast.id).into(); let kind = node::Kind::Operation;
Node { kind, size, children, ast_id, payload } Node { kind, size, ast_id, ..default() }
}; };
// Note that in this place it is impossible that Ast is in form of // Note that in this place it is impossible that Ast is in form of
// `this.method` -- it is covered by the former if arm. As such, we don't // `this.method` -- it is covered by the former if arm. As such, we don't
@ -290,7 +276,7 @@ fn generate_node_for_ast<T: Payload>(
let params = info.parameters.into_iter(); let params = info.parameters.into_iter();
Ok(generate_expected_arguments(node, kind, provided_prefix_arg_count, params)) Ok(generate_expected_arguments(node, kind, provided_prefix_arg_count, params))
} else { } else {
Ok(Node { kind, size, children, ast_id, payload }) Ok(Node { kind, size, ast_id, ..default() })
} }
} }
} }
@ -300,31 +286,55 @@ fn generate_node_for_ast<T: Payload>(
// === Operators (Sections and Infixes) === // === Operators (Sections and Infixes) ===
impl<T: Payload> SpanTreeGenerator<T> for ast::opr::Chain { impl<T: Payload> SpanTreeGenerator<T> for GeneralizedInfix {
fn generate_node( fn generate_node(
&self, &self,
kind: impl Into<node::Kind>, kind: impl Into<node::Kind>,
context: &impl Context, context: &impl Context,
) -> FallibleResult<Node<T>> { ) -> FallibleResult<Node<T>> {
generate_node_for_opr_chain(self, kind.into(), context) // Code like `ast.func` or `a+b+c`.
let chain = self.flatten();
let kind = kind.into();
let mut app_base = ApplicationBase::from_infix(self);
if app_base.uses_method_notation {
// This is a standalone method access chain, missing method parameters needs to be
// handled here. It is guaranteed that no existing prefix arguments are present, as
// method calls inside prefix chains are handled by `generate_node_for_prefix_chain` and
// never reach this point.
let arguments = app_base.known_arguments(context);
let args_resolved = arguments.is_some();
let arguments = arguments.unwrap_or_default();
let arity = arguments.len();
let base_node_kind = if arity == 0 { kind.clone() } else { node::Kind::Operation };
// When arguments were not resolved, clear the call information. Otherwise it would be
// incorrectly assigned to the access chain target span.
if !args_resolved {
app_base.set_call_id(None);
}
let node = generate_node_for_opr_chain(chain, base_node_kind, app_base, context)?;
let provided_prefix_arg_count = 0;
let args_iter = arguments.into_iter();
Ok(generate_expected_arguments(node, kind, provided_prefix_arg_count, args_iter))
} else {
// For non-access infix operators, missing arguments are not handled at this level.
generate_node_for_opr_chain(chain, kind, app_base, context)
}
} }
} }
fn generate_node_for_opr_chain<T: Payload>( fn generate_node_for_opr_chain<T: Payload>(
this: &ast::opr::Chain, this: ast::opr::Chain,
kind: node::Kind, kind: node::Kind,
mut app_base: ApplicationBase,
context: &impl Context, context: &impl Context,
) -> FallibleResult<Node<T>> { ) -> FallibleResult<Node<T>> {
let is_access = this.operator.name == ast::opr::predefined::ACCESS;
let this_call_id =
if is_access { kind.call_id() } else { this.args.first().and_then(|elem| elem.infix_id) };
// Removing operands is possible only when chain has at least 3 of them // Removing operands is possible only when chain has at least 3 of them
// (target and two arguments). // (target and two arguments).
let removable = this.args.len() >= 2; let removable = this.args.len() >= 2;
let node_and_offset: FallibleResult<(Node<T>, usize)> = match &this.target { let node_and_offset: FallibleResult<(Node<T>, usize)> = match &this.target {
Some(target) => { Some(target) => {
let kind = node::Kind::this().with_removable(removable).with_call_id(this_call_id); let kind = node::Kind::this().with_removable(removable);
let node = target.arg.generate_node(kind, context)?; let node = target.arg.generate_node(kind, context)?;
Ok((node, target.offset)) Ok((node, target.offset))
} }
@ -349,21 +359,55 @@ fn generate_node_for_opr_chain<T: Payload>(
if has_target { if has_target {
gen.generate_empty_node(InsertionPointType::BeforeTarget); gen.generate_empty_node(InsertionPointType::BeforeTarget);
} }
gen.add_node(left_crumbs, node); let node = gen.add_node(left_crumbs, node);
if app_base.uses_method_notation && is_last {
// For method notation, the target of outermost chain element is considered the `self`
// argument of the method application.
let target_arg_info = ArgumentInfo::this(None, app_base.call_id);
node.set_argument_info(target_arg_info);
}
let infix_right_argument_info = if !app_base.uses_method_notation {
app_base.set_call_id(elem.infix_id);
app_base.known_arguments(context).and_then(|mut infix_args| {
// For resolved infix arguments, the arity should always be 2. First always
// corresponds to already generated node, and second is the argument that is about
// to be generated.
if infix_args.len() != 2 {
error!("Infix operator should have arity 2, but got {:?}", infix_args.len());
}
let infix_left_argument_info = infix_args.pop_front();
let infix_right_argument_info = infix_args.pop_front();
if let Some(info) = infix_left_argument_info {
node.set_argument_info(info);
}
infix_right_argument_info
})
} else {
None
};
if has_target { if has_target {
gen.generate_empty_node(InsertionPointType::AfterTarget); gen.generate_empty_node(InsertionPointType::AfterTarget);
} }
gen.spacing(off); gen.spacing(off);
gen.generate_ast_node(opr_ast, node::Kind::operation(), context)?; gen.generate_ast_node(opr_ast, node::Kind::Operation, context)?;
if let Some(operand) = &elem.operand { if let Some(operand) = &elem.operand {
let arg_crumbs = elem.crumb_to_operand(has_left); let arg_crumbs = elem.crumb_to_operand(has_left);
let arg_ast = Located::new(arg_crumbs, operand.arg.clone_ref()); let arg_ast = Located::new(arg_crumbs, operand.arg.clone_ref());
gen.spacing(operand.offset); gen.spacing(operand.offset);
let arg_call_id = if is_access { None } else { elem.infix_id }; let argument_kind = node::Kind::argument().with_removable(removable);
let arg = node::Kind::argument().with_removable(removable).with_call_id(arg_call_id); let argument = gen.generate_ast_node(arg_ast, argument_kind, context)?;
gen.generate_ast_node(arg_ast, arg, context)?;
if let Some(info) = infix_right_argument_info {
argument.node.set_argument_info(info);
}
} }
gen.generate_empty_node(InsertionPointType::Append); gen.generate_empty_node(InsertionPointType::Append);
if this.operator.right_assoc { if this.operator.right_assoc {
@ -372,11 +416,12 @@ fn generate_node_for_opr_chain<T: Payload>(
Ok(( Ok((
Node { Node {
kind: if is_last { kind.clone() } else { node::Kind::Chained }, kind: if is_last { kind.clone() } else { node::Kind::chained().into() },
size: gen.current_offset, parenthesized: false,
children: gen.children, size: gen.current_offset,
ast_id: elem.infix_id, children: gen.children,
payload: default(), ast_id: elem.infix_id,
payload: default(),
}, },
elem.offset, elem.offset,
)) ))
@ -403,7 +448,7 @@ fn generate_node_for_prefix_chain<T: Payload>(
context: &impl Context, context: &impl Context,
) -> FallibleResult<Node<T>> { ) -> FallibleResult<Node<T>> {
let app_base = ApplicationBase::from_prefix_chain(this); let app_base = ApplicationBase::from_prefix_chain(this);
let known_params = app_base.known_prefix_arguments(context); let known_params = app_base.known_arguments(context);
let uses_method_notation = app_base.uses_method_notation; let uses_method_notation = app_base.uses_method_notation;
let known_args = known_params.is_some(); let known_args = known_params.is_some();
let mut known_params = known_params.unwrap_or_default(); let mut known_params = known_params.unwrap_or_default();
@ -413,8 +458,17 @@ fn generate_node_for_prefix_chain<T: Payload>(
use ast::crumbs::PrefixCrumb::*; use ast::crumbs::PrefixCrumb::*;
// Removing arguments is possible if there at least two of them // Removing arguments is possible if there at least two of them
let removable = this.args.len() >= 2; let removable = this.args.len() >= 2;
let node =
this.func.generate_node(node::Kind::operation().with_call_id(app_base.call_id), context); // When using method notation, expand the infix access chain manually to maintain correct method
// application info and avoid generating expected arguments twice. We cannot use the
// `generate_node` implementation on `GeneralizedInfix`, because it always treats the
// access chain as a method call without any arguments applied.
let node = if let Some(infix) = GeneralizedInfix::try_new(&this.func) {
generate_node_for_opr_chain(infix.flatten(), node::Kind::Operation, app_base, context)
} else {
this.func.generate_node(node::Kind::Operation, context)
};
let ret = this.args.iter().enumerate().fold(node, |node, (i, arg)| { let ret = this.args.iter().enumerate().fold(node, |node, (i, arg)| {
let node = node?; let node = node?;
let is_first = i == 0; let is_first = i == 0;
@ -441,11 +495,12 @@ fn generate_node_for_prefix_chain<T: Payload>(
gen.generate_empty_node(InsertionPointType::Append); gen.generate_empty_node(InsertionPointType::Append);
} }
Ok(Node { Ok(Node {
kind: if is_last { kind.clone() } else { node::Kind::Chained }, kind: if is_last { kind.clone() } else { node::Kind::chained().into() },
size: gen.current_offset, parenthesized: false,
children: gen.children, size: gen.current_offset,
ast_id: arg.prefix_id, children: gen.children,
payload: default(), ast_id: arg.prefix_id,
payload: default(),
}) })
})?; })?;
@ -472,11 +527,12 @@ fn generate_expected_argument<T: Payload>(
let arg_node = gen.generate_empty_node(InsertionPointType::ExpectedArgument(index)); let arg_node = gen.generate_empty_node(InsertionPointType::ExpectedArgument(index));
arg_node.node.set_argument_info(argument_info); arg_node.node.set_argument_info(argument_info);
Node { Node {
kind: if is_last { kind } else { node::Kind::Chained }, kind: if is_last { kind } else { node::Kind::chained().into() },
size: gen.current_offset, parenthesized: false,
children: gen.children, size: gen.current_offset,
ast_id: None, children: gen.children,
payload: default(), ast_id: None,
payload: default(),
} }
} }
@ -501,14 +557,11 @@ fn generate_expected_arguments<T: Payload>(
fn tree_generate_node<T: Payload>( fn tree_generate_node<T: Payload>(
tree: &ast::Tree<Ast>, tree: &ast::Tree<Ast>,
kind: impl Into<node::Kind>, kind: node::Kind,
context: &impl Context, context: &impl Context,
ast_id: Option<Id>, ast_id: Option<Id>,
) -> FallibleResult<Node<T>> { ) -> FallibleResult<Node<T>> {
let kind = match &tree.type_info { let parenthesized = matches!(tree.type_info, ast::TreeType::Group);
ast::TreeType::Group => node::Kind::Group,
_ => kind.into(),
};
let mut children = vec![]; let mut children = vec![];
let size; let size;
if let Some(leaf_info) = &tree.leaf_info { if let Some(leaf_info) = &tree.leaf_info {
@ -527,13 +580,7 @@ fn tree_generate_node<T: Payload>(
offset += size; offset += size;
} }
SpanSeed::Child(ast::SpanSeedChild { node }) => { SpanSeed::Child(ast::SpanSeedChild { node }) => {
let kind = node::Kind::Argument(node::Argument { let kind = node::Kind::argument();
removable: false,
name: None,
tp: None,
call_id: None,
tag_values: vec![],
});
let node = node.generate_node(kind, context)?; let node = node.generate_node(kind, context)?;
let child_size = node.size; let child_size = node.size;
let ast_crumbs = vec![ast::crumbs::TreeCrumb { index }.into()]; let ast_crumbs = vec![ast::crumbs::TreeCrumb { index }.into()];
@ -545,7 +592,7 @@ fn tree_generate_node<T: Payload>(
size = offset; size = offset;
} }
let payload = default(); let payload = default();
Ok(Node { kind, size, children, ast_id, payload }) Ok(Node { kind, parenthesized, size, children, ast_id, payload })
} }
@ -655,9 +702,9 @@ mod test {
.add_empty_child(0, BeforeTarget) .add_empty_child(0, BeforeTarget)
.add_leaf(0, 1, node::Kind::this(), InfixCrumb::LeftOperand) .add_leaf(0, 1, node::Kind::this(), InfixCrumb::LeftOperand)
.add_empty_child(1, AfterTarget) .add_empty_child(1, AfterTarget)
.add_leaf(2, 1, node::Kind::operation(), InfixCrumb::Operator) .add_leaf(2, 1, node::Kind::Operation, InfixCrumb::Operator)
.add_child(4, 7, node::Kind::argument(), InfixCrumb::RightOperand) .add_child(4, 7, node::Kind::argument(), InfixCrumb::RightOperand)
.add_leaf(0, 3, node::Kind::operation(), PrefixCrumb::Func) .add_leaf(0, 3, node::Kind::Operation, PrefixCrumb::Func)
.add_empty_child(4, BeforeTarget) .add_empty_child(4, BeforeTarget)
.add_leaf(4, 3, node::Kind::this(), PrefixCrumb::Arg) .add_leaf(4, 3, node::Kind::this(), PrefixCrumb::Arg)
.add_empty_child(7, Append) .add_empty_child(7, Append)
@ -665,7 +712,7 @@ mod test {
.add_empty_child(11, Append) .add_empty_child(11, Append)
.done() .done()
.add_empty_child(11, AfterTarget) .add_empty_child(11, AfterTarget)
.add_leaf(12, 1, node::Kind::operation(), InfixCrumb::Operator) .add_leaf(12, 1, node::Kind::Operation, InfixCrumb::Operator)
.add_leaf(14, 1, node::Kind::argument(), InfixCrumb::RightOperand) .add_leaf(14, 1, node::Kind::argument(), InfixCrumb::RightOperand)
.add_empty_child(15, Append) .add_empty_child(15, Append)
.build(); .build();
@ -682,20 +729,20 @@ mod test {
clear_parameter_infos(&mut tree.root); clear_parameter_infos(&mut tree.root);
let expected = TreeBuilder::new(26) let expected = TreeBuilder::new(26)
.add_child(0, 22, node::Kind::Chained, InfixCrumb::LeftOperand) .add_child(0, 22, node::Kind::chained(), InfixCrumb::LeftOperand)
.add_child(0, 5, node::Kind::Chained, InfixCrumb::LeftOperand) .add_child(0, 5, node::Kind::chained(), InfixCrumb::LeftOperand)
.add_empty_child(0, BeforeTarget) .add_empty_child(0, BeforeTarget)
.add_leaf(0, 1, node::Kind::this().removable(), InfixCrumb::LeftOperand) .add_leaf(0, 1, node::Kind::this().removable(), InfixCrumb::LeftOperand)
.add_empty_child(1, AfterTarget) .add_empty_child(1, AfterTarget)
.add_leaf(2, 1, node::Kind::operation(), InfixCrumb::Operator) .add_leaf(2, 1, node::Kind::Operation, InfixCrumb::Operator)
.add_leaf(4, 1, node::Kind::argument().removable(), InfixCrumb::RightOperand) .add_leaf(4, 1, node::Kind::argument().removable(), InfixCrumb::RightOperand)
.add_empty_child(5, Append) .add_empty_child(5, Append)
.done() .done()
.add_leaf(6, 1, node::Kind::operation(), InfixCrumb::Operator) .add_leaf(6, 1, node::Kind::Operation, InfixCrumb::Operator)
.add_child(8, 14, node::Kind::argument().removable(), InfixCrumb::RightOperand) .add_child(8, 14, node::Kind::argument().removable(), InfixCrumb::RightOperand)
.add_child(0, 11, node::Kind::Chained, PrefixCrumb::Func) .add_child(0, 11, node::Kind::chained(), PrefixCrumb::Func)
.add_child(0, 7, node::Kind::Chained, PrefixCrumb::Func) .add_child(0, 7, node::Kind::chained(), PrefixCrumb::Func)
.add_leaf(0, 3, node::Kind::operation(), PrefixCrumb::Func) .add_leaf(0, 3, node::Kind::Operation, PrefixCrumb::Func)
.add_empty_child(4, BeforeTarget) .add_empty_child(4, BeforeTarget)
.add_leaf(4, 3, node::Kind::this().removable(), PrefixCrumb::Arg) .add_leaf(4, 3, node::Kind::this().removable(), PrefixCrumb::Arg)
.add_empty_child(7, Append) .add_empty_child(7, Append)
@ -708,7 +755,7 @@ mod test {
.done() .done()
.add_empty_child(22, Append) .add_empty_child(22, Append)
.done() .done()
.add_leaf(23, 1, node::Kind::operation(), InfixCrumb::Operator) .add_leaf(23, 1, node::Kind::Operation, InfixCrumb::Operator)
.add_leaf(25, 1, node::Kind::argument().removable(), InfixCrumb::RightOperand) .add_leaf(25, 1, node::Kind::argument().removable(), InfixCrumb::RightOperand)
.add_empty_child(26, Append) .add_empty_child(26, Append)
.build(); .build();
@ -727,11 +774,11 @@ mod test {
let expected = TreeBuilder::new(7) let expected = TreeBuilder::new(7)
.add_empty_child(0, Append) .add_empty_child(0, Append)
.add_leaf(0, 1, node::Kind::argument().removable(), InfixCrumb::LeftOperand) .add_leaf(0, 1, node::Kind::argument().removable(), InfixCrumb::LeftOperand)
.add_leaf(1, 2, node::Kind::operation(), InfixCrumb::Operator) .add_leaf(1, 2, node::Kind::Operation, InfixCrumb::Operator)
.add_child(3, 3, node::Kind::Chained, InfixCrumb::RightOperand) .add_child(3, 3, node::Kind::chained(), InfixCrumb::RightOperand)
.add_empty_child(0, Append) .add_empty_child(0, Append)
.add_leaf(0, 1, node::Kind::argument().removable(), InfixCrumb::LeftOperand) .add_leaf(0, 1, node::Kind::argument().removable(), InfixCrumb::LeftOperand)
.add_leaf(1, 2, node::Kind::operation(), InfixCrumb::Operator) .add_leaf(1, 2, node::Kind::Operation, InfixCrumb::Operator)
.add_empty_child(3, AfterTarget) .add_empty_child(3, AfterTarget)
.add_leaf(3, 1, node::Kind::this().removable(), InfixCrumb::RightOperand) .add_leaf(3, 1, node::Kind::this().removable(), InfixCrumb::RightOperand)
.add_empty_child(4, BeforeTarget) .add_empty_child(4, BeforeTarget)
@ -750,11 +797,11 @@ mod test {
clear_parameter_infos(&mut tree.root); clear_parameter_infos(&mut tree.root);
let expected = TreeBuilder::new(5) let expected = TreeBuilder::new(5)
.add_empty_child(0, Append) .add_empty_child(0, Append)
.add_leaf(0, 2, node::Kind::operation(), SectionRightCrumb::Opr) .add_leaf(0, 2, node::Kind::Operation, SectionRightCrumb::Opr)
.add_child(2, 2, node::Kind::Chained, SectionRightCrumb::Arg) .add_child(2, 2, node::Kind::chained(), SectionRightCrumb::Arg)
.add_empty_child(0, Append) .add_empty_child(0, Append)
.add_leaf(0, 1, node::Kind::argument().removable(), SectionLeftCrumb::Arg) .add_leaf(0, 1, node::Kind::argument().removable(), SectionLeftCrumb::Arg)
.add_leaf(1, 1, node::Kind::operation(), SectionLeftCrumb::Opr) .add_leaf(1, 1, node::Kind::Operation, SectionLeftCrumb::Opr)
.add_empty_child(2, BeforeTarget) .add_empty_child(2, BeforeTarget)
.done() .done()
.build(); .build();
@ -770,7 +817,7 @@ mod test {
clear_parameter_infos(&mut tree.root); clear_parameter_infos(&mut tree.root);
let expected = TreeBuilder::new(13) let expected = TreeBuilder::new(13)
.add_leaf(0, 3, node::Kind::operation(), PrefixCrumb::Func) .add_leaf(0, 3, node::Kind::Operation, PrefixCrumb::Func)
.add_empty_child(4, BeforeTarget) .add_empty_child(4, BeforeTarget)
.add_leaf(4, 9, node::Kind::this(), PrefixCrumb::Arg) .add_leaf(4, 9, node::Kind::this(), PrefixCrumb::Arg)
.add_empty_child(13, Append) .add_empty_child(13, Append)
@ -807,7 +854,7 @@ mod test {
let mut id_map = IdMap::default(); let mut id_map = IdMap::default();
let call_id = id_map.generate(0..3); let call_id = id_map.generate(0..3);
let ast = parser.parse_line_ast_with_id_map("foo", id_map).unwrap(); let ast = parser.parse_line_ast_with_id_map("foo", id_map).unwrap();
let invocation_info = CalledMethodInfo { parameters: vec![this_param(None)] }; let invocation_info = CalledMethodInfo { parameters: vec![this_param(None)], ..default() };
let ctx = MockContext::new_single(ast.id.unwrap(), invocation_info); let ctx = MockContext::new_single(ast.id.unwrap(), invocation_info);
let mut tree: SpanTree = SpanTree::new(&ast, &ctx).unwrap(); let mut tree: SpanTree = SpanTree::new(&ast, &ctx).unwrap();
match tree.root_ref().leaf_iter().collect_vec().as_slice() { match tree.root_ref().leaf_iter().collect_vec().as_slice() {
@ -815,7 +862,7 @@ mod test {
sth_else => panic!("There should be 2 leaves, found: {}", sth_else.len()), sth_else => panic!("There should be 2 leaves, found: {}", sth_else.len()),
} }
let expected = TreeBuilder::new(3) let expected = TreeBuilder::new(3)
.add_leaf(0, 3, node::Kind::operation(), Crumbs::default()) .add_leaf(0, 3, node::Kind::Operation, Crumbs::default())
.add_empty_child(3, ExpectedArgument(0)) .add_empty_child(3, ExpectedArgument(0))
.build(); .build();
clear_expression_ids(&mut tree.root); clear_expression_ids(&mut tree.root);
@ -828,7 +875,7 @@ mod test {
let mut id_map = IdMap::default(); let mut id_map = IdMap::default();
let call_id = id_map.generate(0..8); let call_id = id_map.generate(0..8);
let ast = parser.parse_line_ast_with_id_map("foo here", id_map).unwrap(); let ast = parser.parse_line_ast_with_id_map("foo here", id_map).unwrap();
let invocation_info = CalledMethodInfo { parameters: vec![this_param(None)] }; let invocation_info = CalledMethodInfo { parameters: vec![this_param(None)], ..default() };
let ctx = MockContext::new_single(ast.id.unwrap(), invocation_info); let ctx = MockContext::new_single(ast.id.unwrap(), invocation_info);
let mut tree: SpanTree = SpanTree::new(&ast, &ctx).unwrap(); let mut tree: SpanTree = SpanTree::new(&ast, &ctx).unwrap();
match tree.root_ref().leaf_iter().collect_vec().as_slice() { match tree.root_ref().leaf_iter().collect_vec().as_slice() {
@ -836,7 +883,7 @@ mod test {
sth_else => panic!("There should be 2 leaves, found: {}", sth_else.len()), sth_else => panic!("There should be 2 leaves, found: {}", sth_else.len()),
} }
let expected = TreeBuilder::new(8) let expected = TreeBuilder::new(8)
.add_leaf(0, 3, node::Kind::operation(), PrefixCrumb::Func) .add_leaf(0, 3, node::Kind::Operation, PrefixCrumb::Func)
.add_leaf(4, 4, node::Kind::this(), PrefixCrumb::Arg) .add_leaf(4, 4, node::Kind::this(), PrefixCrumb::Arg)
.build(); .build();
clear_expression_ids(&mut tree.root); clear_expression_ids(&mut tree.root);
@ -849,8 +896,10 @@ mod test {
let mut id_map = IdMap::default(); let mut id_map = IdMap::default();
let call_id = Some(id_map.generate(0..8)); let call_id = Some(id_map.generate(0..8));
let ast = parser.parse_line_ast_with_id_map("foo here", id_map).unwrap(); let ast = parser.parse_line_ast_with_id_map("foo here", id_map).unwrap();
let invocation_info = let invocation_info = CalledMethodInfo {
CalledMethodInfo { parameters: vec![this_param(None), param1(None), param2(None)] }; parameters: vec![this_param(None), param1(None), param2(None)],
..default()
};
let ctx = MockContext::new_single(ast.id.unwrap(), invocation_info); let ctx = MockContext::new_single(ast.id.unwrap(), invocation_info);
let mut tree: SpanTree = SpanTree::new(&ast, &ctx).unwrap(); let mut tree: SpanTree = SpanTree::new(&ast, &ctx).unwrap();
match tree.root_ref().leaf_iter().collect_vec().as_slice() { match tree.root_ref().leaf_iter().collect_vec().as_slice() {
@ -862,9 +911,9 @@ mod test {
sth_else => panic!("There should be 4 leaves, found: {}", sth_else.len()), sth_else => panic!("There should be 4 leaves, found: {}", sth_else.len()),
} }
let expected = TreeBuilder::new(8) let expected = TreeBuilder::new(8)
.add_child(0, 8, node::Kind::Chained, Crumbs::default()) .add_child(0, 8, node::Kind::chained(), Crumbs::default())
.add_child(0, 8, node::Kind::Chained, Crumbs::default()) .add_child(0, 8, node::Kind::chained(), Crumbs::default())
.add_leaf(0, 3, node::Kind::operation(), PrefixCrumb::Func) .add_leaf(0, 3, node::Kind::Operation, PrefixCrumb::Func)
.add_leaf(4, 4, node::Kind::this(), PrefixCrumb::Arg) .add_leaf(4, 4, node::Kind::this(), PrefixCrumb::Arg)
.done() .done()
.add_empty_child(8, ExpectedArgument(1)) .add_empty_child(8, ExpectedArgument(1))
@ -880,8 +929,10 @@ mod test {
let mut id_map = IdMap::default(); let mut id_map = IdMap::default();
let call_id = Some(id_map.generate(0..8)); let call_id = Some(id_map.generate(0..8));
let ast = parser.parse_line_ast_with_id_map("here.foo", id_map).unwrap(); let ast = parser.parse_line_ast_with_id_map("here.foo", id_map).unwrap();
let invocation_info = let invocation_info = CalledMethodInfo {
CalledMethodInfo { parameters: vec![this_param(None), param1(None), param2(None)] }; parameters: vec![this_param(None), param1(None), param2(None)],
..default()
};
let ctx = MockContext::new_single(ast.id.unwrap(), invocation_info); let ctx = MockContext::new_single(ast.id.unwrap(), invocation_info);
let mut tree: SpanTree = SpanTree::new(&ast, &ctx).unwrap(); let mut tree: SpanTree = SpanTree::new(&ast, &ctx).unwrap();
match tree.root_ref().leaf_iter().collect_vec().as_slice() { match tree.root_ref().leaf_iter().collect_vec().as_slice() {
@ -892,12 +943,12 @@ mod test {
sth_else => panic!("There should be 8 leaves, found: {}", sth_else.len()), sth_else => panic!("There should be 8 leaves, found: {}", sth_else.len()),
} }
let expected = TreeBuilder::new(8) let expected = TreeBuilder::new(8)
.add_child(0, 8, node::Kind::Chained, Crumbs::default()) .add_child(0, 8, node::Kind::chained(), Crumbs::default())
.add_child(0, 8, node::Kind::operation(), Crumbs::default()) .add_child(0, 8, node::Kind::Operation, Crumbs::default())
.add_empty_child(0, BeforeTarget) .add_empty_child(0, BeforeTarget)
.add_leaf(0, 4, node::Kind::this(), InfixCrumb::LeftOperand) .add_leaf(0, 4, node::Kind::this(), InfixCrumb::LeftOperand)
.add_empty_child(4, AfterTarget) .add_empty_child(4, AfterTarget)
.add_leaf(4, 1, node::Kind::operation(), InfixCrumb::Operator) .add_leaf(4, 1, node::Kind::Operation, InfixCrumb::Operator)
.add_leaf(5, 3, node::Kind::argument(), InfixCrumb::RightOperand) .add_leaf(5, 3, node::Kind::argument(), InfixCrumb::RightOperand)
.add_empty_child(8, Append) .add_empty_child(8, Append)
.done() .done()

View File

@ -11,10 +11,19 @@ use ast::Id;
/// Additional information available on nodes that are an invocation of a known methods. /// Additional information available on nodes that are an invocation of a known methods.
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq, Default)]
pub struct CalledMethodInfo { pub struct CalledMethodInfo {
/// Whether or not this call represents a static method.
pub is_static: bool,
/// Whether or not this call represents a constructor.
pub is_constructor: bool,
/// Whether or not this method was called as a qualified function (e.g. `Text.trim`).
/// This also applies to all static methods, as those are always called that way. This
/// information is only available when it is resolved from computed value registry for specific
/// call expression. Otherwise it is `None`.
pub called_on_type: Option<bool>,
/// Information about arguments taken by a called method. /// Information about arguments taken by a called method.
pub parameters: Vec<ArgumentInfo>, pub parameters: Vec<ArgumentInfo>,
} }
impl CalledMethodInfo { impl CalledMethodInfo {
@ -25,6 +34,12 @@ impl CalledMethodInfo {
}); });
self self
} }
/// Add information whether this method was called on .
pub fn with_called_on_type(mut self, called_on_type: bool) -> Self {
self.called_on_type = Some(called_on_type);
self
}
} }

View File

@ -94,7 +94,7 @@ impl<'a, T> LeafIterator<'a, T> {
fn can_descend(&self, current_node: &Node<T>) -> bool { fn can_descend(&self, current_node: &Node<T>) -> bool {
match &self.fragment { match &self.fragment {
TreeFragment::AllNodes => true, TreeFragment::AllNodes => true,
TreeFragment::ChainAndDirectChildren => current_node.kind == node::Kind::Chained, TreeFragment::ChainAndDirectChildren => current_node.kind.is_chained(),
} }
} }
} }
@ -119,7 +119,6 @@ mod tests {
use ast::crumbs::InfixCrumb::*; use ast::crumbs::InfixCrumb::*;
use ast::crumbs::PrefixCrumb::*; use ast::crumbs::PrefixCrumb::*;
use node::Kind; use node::Kind;
use node::Kind::*;
// Tree we use for tests (C means chained nodes): // Tree we use for tests (C means chained nodes):
// root: (-) // root: (-)
@ -131,21 +130,21 @@ mod tests {
// gg-children: ()() ()() () // gg-children: ()() ()() ()
let tree: SpanTree = TreeBuilder::new(14) let tree: SpanTree = TreeBuilder::new(14)
.add_child(0, 10, Chained, vec![LeftOperand]) .add_child(0, 10, Kind::chained(), vec![LeftOperand])
.add_leaf(0, 3, Kind::this(), vec![LeftOperand]) .add_leaf(0, 3, Kind::this(), vec![LeftOperand])
.add_leaf(4, 1, Kind::operation(), vec![Operator]) .add_leaf(4, 1, Kind::Operation, vec![Operator])
.add_child(6, 3, Kind::argument(), vec![RightOperand]) .add_child(6, 3, Kind::argument(), vec![RightOperand])
.add_leaf(0, 1, Kind::operation(), vec![Func]) .add_leaf(0, 1, Kind::Operation, vec![Func])
.add_leaf(2, 1, Kind::this(), vec![Arg]) .add_leaf(2, 1, Kind::this(), vec![Arg])
.done() .done()
.done() .done()
.add_leaf(11, 1, Kind::operation(), vec![Operator]) .add_leaf(11, 1, Kind::Operation, vec![Operator])
.add_child(13, 1, Chained, vec![RightOperand]) .add_child(13, 1, Kind::chained(), vec![RightOperand])
.add_leaf(0, 3, Kind::this(), vec![LeftOperand]) .add_leaf(0, 3, Kind::this(), vec![LeftOperand])
.add_leaf(4, 1, Kind::operation(), vec![Operator]) .add_leaf(4, 1, Kind::Operation, vec![Operator])
.add_child(6, 5, Chained, vec![RightOperand]) .add_child(6, 5, Kind::chained(), vec![RightOperand])
.add_leaf(0, 1, Kind::this(), vec![LeftOperand]) .add_leaf(0, 1, Kind::this(), vec![LeftOperand])
.add_leaf(2, 1, Kind::operation(), vec![Operator]) .add_leaf(2, 1, Kind::Operation, vec![Operator])
.add_leaf(4, 1, Kind::argument(), vec![RightOperand]) .add_leaf(4, 1, Kind::argument(), vec![RightOperand])
.done() .done()
.done() .done()

View File

@ -35,11 +35,12 @@ pub trait Payload = Default + Clone;
#[derive(Clone, Debug, Default, Eq, PartialEq)] #[derive(Clone, Debug, Default, Eq, PartialEq)]
#[allow(missing_docs)] #[allow(missing_docs)]
pub struct Node<T> { pub struct Node<T> {
pub kind: Kind, pub kind: Kind,
pub size: ByteDiff, pub size: ByteDiff,
pub children: Vec<Child<T>>, pub children: Vec<Child<T>>,
pub ast_id: Option<ast::Id>, pub ast_id: Option<ast::Id>,
pub payload: T, pub parenthesized: bool,
pub payload: T,
} }
impl<T> Deref for Node<T> { impl<T> Deref for Node<T> {
@ -67,11 +68,12 @@ impl<T: Payload> Node<T> {
/// Payload mapping utility. /// Payload mapping utility.
pub fn map<S>(self, f: impl Copy + Fn(T) -> S) -> Node<S> { pub fn map<S>(self, f: impl Copy + Fn(T) -> S) -> Node<S> {
let kind = self.kind; let kind = self.kind;
let parenthesized = self.parenthesized;
let size = self.size; let size = self.size;
let children = self.children.into_iter().map(|t| t.map(f)).collect_vec(); let children = self.children.into_iter().map(|t| t.map(f)).collect_vec();
let ast_id = self.ast_id; let ast_id = self.ast_id;
let payload = f(self.payload); let payload = f(self.payload);
Node { kind, size, children, ast_id, payload } Node { kind, parenthesized, size, children, ast_id, payload }
} }
} }
@ -79,8 +81,8 @@ impl<T: Payload> Node<T> {
#[allow(missing_docs)] #[allow(missing_docs)]
impl<T: Payload> Node<T> { impl<T: Payload> Node<T> {
pub fn is_parensed(&self) -> bool { pub fn parenthesized(&self) -> bool {
self.kind == Kind::Group self.parenthesized
} }
pub fn is_root(&self) -> bool { pub fn is_root(&self) -> bool {
self.kind.is_root() self.kind.is_root()
@ -745,10 +747,10 @@ mod test {
let tree: SpanTree = TreeBuilder::new(7) let tree: SpanTree = TreeBuilder::new(7)
.add_leaf(0, 1, node::Kind::this(), vec![LeftOperand]) .add_leaf(0, 1, node::Kind::this(), vec![LeftOperand])
.add_leaf(1, 1, node::Kind::operation(), vec![Operator]) .add_leaf(1, 1, node::Kind::Operation, vec![Operator])
.add_child(2, 5, node::Kind::argument(), vec![RightOperand]) .add_child(2, 5, node::Kind::argument(), vec![RightOperand])
.add_leaf(0, 2, node::Kind::this(), vec![LeftOperand]) .add_leaf(0, 2, node::Kind::this(), vec![LeftOperand])
.add_leaf(3, 1, node::Kind::operation(), vec![Operator]) .add_leaf(3, 1, node::Kind::Operation, vec![Operator])
.add_leaf(4, 1, node::Kind::argument(), vec![RightOperand]) .add_leaf(4, 1, node::Kind::argument(), vec![RightOperand])
.done() .done()
.build(); .build();
@ -804,9 +806,9 @@ mod test {
let tree: SpanTree = TreeBuilder::new(7) let tree: SpanTree = TreeBuilder::new(7)
.add_leaf(0, 1, node::Kind::this(), vec![LeftOperand]) .add_leaf(0, 1, node::Kind::this(), vec![LeftOperand])
.add_empty_child(1, InsertionPointType::AfterTarget) .add_empty_child(1, InsertionPointType::AfterTarget)
.add_leaf(1, 1, node::Kind::operation(), vec![Operator]) .add_leaf(1, 1, node::Kind::Operation, vec![Operator])
.add_child(2, 5, node::Kind::argument(), vec![RightOperand]) .add_child(2, 5, node::Kind::argument(), vec![RightOperand])
.add_leaf(0, 3, node::Kind::operation(), vec![Func]) .add_leaf(0, 3, node::Kind::Operation, vec![Func])
.add_leaf(3, 1, node::Kind::this(), vec![Arg]) .add_leaf(3, 1, node::Kind::this(), vec![Arg])
.done() .done()
.build(); .build();
@ -835,10 +837,10 @@ mod test {
// An example with single call and expected arguments. // An example with single call and expected arguments.
// See also `generate::test::generating_span_tree_for_unfinished_call` // See also `generate::test::generating_span_tree_for_unfinished_call`
let tree: SpanTree = TreeBuilder::new(8) let tree: SpanTree = TreeBuilder::new(8)
.add_child(0, 8, node::Kind::Chained, ast::crumbs::Crumbs::default()) .add_child(0, 8, node::Kind::chained(), ast::crumbs::Crumbs::default())
.add_child(0, 8, node::Kind::operation(), ast::crumbs::Crumbs::default()) .add_child(0, 8, node::Kind::Operation, ast::crumbs::Crumbs::default())
.add_leaf(0, 4, node::Kind::this(), LeftOperand) .add_leaf(0, 4, node::Kind::this(), LeftOperand)
.add_leaf(4, 1, node::Kind::operation(), Operator) .add_leaf(4, 1, node::Kind::Operation, Operator)
.add_leaf(5, 3, node::Kind::argument(), RightOperand) .add_leaf(5, 3, node::Kind::argument(), RightOperand)
.done() .done()
.add_empty_child(8, InsertionPointType::ExpectedArgument(0)) .add_empty_child(8, InsertionPointType::ExpectedArgument(0))

View File

@ -17,9 +17,9 @@ pub enum Kind {
/// A root of the expression tree. /// A root of the expression tree.
Root, Root,
/// A node chained with parent node. See crate's docs for more info about chaining. /// A node chained with parent node. See crate's docs for more info about chaining.
Chained, Chained(Chained),
/// A node representing operation (operator or function) of parent Infix, Section or Prefix. /// A node representing operation (operator or function) of parent Infix, Section or Prefix.
Operation(Operation), Operation,
/// A node being a target (or "self") parameter of parent Infix, Section or Prefix. /// A node being a target (or "self") parameter of parent Infix, Section or Prefix.
This(This), This(This),
/// A node being a normal (not target) parameter of parent Infix, Section or Prefix. /// A node being a normal (not target) parameter of parent Infix, Section or Prefix.
@ -31,8 +31,6 @@ pub enum Kind {
/// between AST tokens. For example, given expression `foo bar`, the span assigned to the /// between AST tokens. For example, given expression `foo bar`, the span assigned to the
/// `InsertionPoint` between `foo` and `bar` should be set to 3. /// `InsertionPoint` between `foo` and `bar` should be set to 3.
InsertionPoint(InsertionPoint), InsertionPoint(InsertionPoint),
/// A parenthesized expression.
Group,
} }
@ -40,7 +38,7 @@ pub enum Kind {
#[allow(missing_docs)] #[allow(missing_docs)]
impl Kind { impl Kind {
pub fn operation() -> Operation { pub fn chained() -> Chained {
default() default()
} }
pub fn this() -> This { pub fn this() -> This {
@ -150,7 +148,7 @@ impl Kind {
/// Get the function call AST ID associated with this argument. /// Get the function call AST ID associated with this argument.
pub fn call_id(&self) -> Option<ast::Id> { pub fn call_id(&self) -> Option<ast::Id> {
match self { match self {
Self::Operation(t) => t.call_id, Self::Chained(t) => t.call_id,
Self::This(t) => t.call_id, Self::This(t) => t.call_id,
Self::Argument(t) => t.call_id, Self::Argument(t) => t.call_id,
Self::InsertionPoint(t) => t.call_id, Self::InsertionPoint(t) => t.call_id,
@ -162,7 +160,7 @@ impl Kind {
/// or was skipped. /// or was skipped.
pub fn set_argument_info(&mut self, argument_info: ArgumentInfo) -> bool { pub fn set_argument_info(&mut self, argument_info: ArgumentInfo) -> bool {
match self { match self {
Self::Operation(t) => { Self::Chained(t) => {
t.call_id = argument_info.call_id; t.call_id = argument_info.call_id;
true true
} }
@ -193,13 +191,12 @@ impl Kind {
pub fn variant_name(&self) -> &str { pub fn variant_name(&self) -> &str {
match self { match self {
Self::Root => "Root", Self::Root => "Root",
Self::Chained => "Chained", Self::Chained(_) => "Chained",
Self::Operation(_) => "Operation", Self::Operation => "Operation",
Self::This(_) => "This", Self::This(_) => "This",
Self::Argument(_) => "Argument", Self::Argument(_) => "Argument",
Self::Token => "Token", Self::Token => "Token",
Self::InsertionPoint(_) => "InsertionPoint", Self::InsertionPoint(_) => "InsertionPoint",
Self::Group => "Group",
} }
} }
} }
@ -214,34 +211,35 @@ impl Default for Kind {
} }
// ================= // ===============
// === Operation === // === Chained ===
// ================= // ===============
/// Kind representing an operation (operator or function) of parent Infix, Section or Prefix. /// A node chained with parent node, potentially being a first argument of a nested infix call
/// expression.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct Operation { pub struct Chained {
/// The AST ID of function application that this operation is part of. If this is an access /// The AST ID of function application that this Chained is a target of. If this is a part of
/// chain operation (e.g. `method.call arg`), the call will be the outermost expression /// an infix operator chain (e.g. `1 + 2 + 3`) and this chained represents `1 + 2`
/// containing all arguments. For other infix operators (not access), the call will be the /// subexpression, it is effectively a target (`self`) argument of the second `+` operator.
/// infix expression containing two arguments. /// In that case the `call_id` will point at its parent `1 + 2 + 3` expression.
pub call_id: Option<ast::Id>, pub call_id: Option<ast::Id>,
} }
// === Setters === // === Setters ===
impl Operation { impl Chained {
/// Set operation `call_id` field. See [`Operation::call_id`] for more information. /// Set Chained `call_id` field. See [`Chained::call_id`] for more information.
pub fn with_call_id(mut self, call_id: Option<ast::Id>) -> Self { pub fn with_call_id(mut self, call_id: Option<ast::Id>) -> Self {
self.call_id = call_id; self.call_id = call_id;
self self
} }
} }
impl From<Operation> for Kind { impl From<Chained> for Kind {
fn from(t: Operation) -> Self { fn from(t: Chained) -> Self {
Self::Operation(t) Self::Chained(t)
} }
} }

View File

@ -421,7 +421,7 @@ impl EndpointInfo {
// Unpleasant. Likely there should be something in span tree that allows obtaining // Unpleasant. Likely there should be something in span tree that allows obtaining
// sequence of nodes between root and given crumb. Or sth. // sequence of nodes between root and given crumb. Or sth.
let mut parent_port = self.parent_port_of(&self.endpoint.port); let mut parent_port = self.parent_port_of(&self.endpoint.port);
while parent_port.contains_if(|p| p.node.kind == span_tree::node::Kind::Chained) { while parent_port.contains_if(|p| p.node.is_chained()) {
parent_port = parent_port.and_then(|p| self.parent_port_of(&p.crumbs)); parent_port = parent_port.and_then(|p| self.parent_port_of(&p.crumbs));
} }
parent_port parent_port

View File

@ -339,8 +339,21 @@ impl Context for Handle {
let lookup_registry = || { let lookup_registry = || {
let info = self.computed_value_info_registry().get(&id)?; let info = self.computed_value_info_registry().get(&id)?;
let method_call = info.method_call.as_ref()?; let method_call = info.method_call.as_ref()?;
let entry = self.project.suggestion_db().lookup_by_method_pointer(method_call)?; let suggestion_db = self.project.suggestion_db();
Some(entry.invocation_info()) let maybe_entry = suggestion_db
.lookup_by_method_pointer(method_call)
.map(|e| e.invocation_info().with_called_on_type(false));
// When the entry was not resolved but the `defined_on_type` has a `.type` suffix,
// try resolving it again with the suffix stripped. This indicates that a method was
// called on type, either because it is a static method, or because it uses qualified
// method syntax.
maybe_entry.or_else(|| {
let defined_on_type = method_call.defined_on_type.strip_suffix(".type")?.to_owned();
let method_call = MethodPointer { defined_on_type, ..method_call.clone() };
let entry = suggestion_db.lookup_by_method_pointer(&method_call)?;
Some(entry.invocation_info().with_called_on_type(true))
})
}; };
let fallback = || self.graph.borrow().call_info(id, name); let fallback = || self.graph.borrow().call_info(id, name);
lookup_registry().or_else(fallback) lookup_registry().or_else(fallback)

View File

@ -383,10 +383,9 @@ impl QueryData {
/// Generate visualization metadata for this query. /// Generate visualization metadata for this query.
fn visualization_metadata(&self) -> Metadata { fn visualization_metadata(&self) -> Metadata {
let relevant_arguments = self.arguments.split_first().map_or_default(|(_self, args)| args);
let arguments: Vec<Code> = vec![ let arguments: Vec<Code> = vec![
Self::escape_visualization_argument(&self.method_name).into(), Self::escape_visualization_argument(&self.method_name).into(),
Self::arg_sequence(relevant_arguments).into(), Self::arg_sequence(&self.arguments).into(),
]; ];
let preprocessor = visualization::instance::PreprocessorConfiguration { let preprocessor = visualization::instance::PreprocessorConfiguration {

View File

@ -826,7 +826,8 @@ impl From<language_server::types::SuggestionEntry> for Entry {
impl TryFrom<&Entry> for language_server::MethodPointer { impl TryFrom<&Entry> for language_server::MethodPointer {
type Error = failure::Error; type Error = failure::Error;
fn try_from(entry: &Entry) -> FallibleResult<Self> { fn try_from(entry: &Entry) -> FallibleResult<Self> {
(entry.kind == Kind::Method).ok_or_else(|| NotAMethod(entry.name.clone()))?; let is_method_or_constructor = matches!(entry.kind, Kind::Method | Kind::Constructor);
is_method_or_constructor.ok_or_else(|| NotAMethod(entry.name.clone()))?;
let missing_this_err = || MissingSelfOnMethod(entry.name.clone()); let missing_this_err = || MissingSelfOnMethod(entry.name.clone());
let defined_on_type = entry.self_type.clone().ok_or_else(missing_this_err)?; let defined_on_type = entry.self_type.clone().ok_or_else(missing_this_err)?;
Ok(language_server::MethodPointer { Ok(language_server::MethodPointer {
@ -847,7 +848,14 @@ impl TryFrom<Entry> for language_server::MethodPointer {
impl From<&Entry> for span_tree::generate::context::CalledMethodInfo { impl From<&Entry> for span_tree::generate::context::CalledMethodInfo {
fn from(entry: &Entry) -> span_tree::generate::context::CalledMethodInfo { fn from(entry: &Entry) -> span_tree::generate::context::CalledMethodInfo {
let parameters = entry.arguments.iter().map(to_span_tree_param).collect(); let parameters = entry.arguments.iter().map(to_span_tree_param).collect();
span_tree::generate::context::CalledMethodInfo { parameters } let is_static = entry.is_static;
let is_constructor = matches!(entry.kind, Kind::Constructor);
span_tree::generate::context::CalledMethodInfo {
is_static,
is_constructor,
parameters,
..default()
}
} }
} }

View File

@ -399,10 +399,8 @@ impl SuggestionDatabase {
&self, &self,
method_pointer: &language_server::MethodPointer, method_pointer: &language_server::MethodPointer,
) -> Option<Rc<Entry>> { ) -> Option<Rc<Entry>> {
self.method_pointer_to_id_map let entry_id = self.method_pointer_to_id_map.borrow().get(method_pointer).copied();
.borrow() entry_id.and_then(|id| self.entries.borrow().get(&id).cloned())
.get(method_pointer)
.and_then(|id| self.entries.borrow().get(id).cloned())
} }
/// Get suggestion entry id by method pointer. /// Get suggestion entry id by method pointer.

View File

@ -322,7 +322,8 @@ pub fn expression_mock_string(label: &str) -> Expression {
let parser = Parser::new(); let parser = Parser::new();
let parameters = vec![]; let parameters = vec![];
let ast = parser.parse_line_ast(&code).unwrap(); let ast = parser.parse_line_ast(&code).unwrap();
let invocation_info = span_tree::generate::context::CalledMethodInfo { parameters }; let invocation_info =
span_tree::generate::context::CalledMethodInfo { parameters, ..default() };
let ctx = span_tree::generate::MockContext::new_single(ast.id.unwrap(), invocation_info); let ctx = span_tree::generate::MockContext::new_single(ast.id.unwrap(), invocation_info);
let output_span_tree = span_tree::SpanTree::default(); let output_span_tree = span_tree::SpanTree::default();
let input_span_tree = span_tree::SpanTree::new(&ast, &ctx).unwrap(); let input_span_tree = span_tree::SpanTree::new(&ast, &ctx).unwrap();
@ -342,7 +343,8 @@ pub fn expression_mock() -> Expression {
}; };
let parameters = vec![this_param]; let parameters = vec![this_param];
let ast = parser.parse_line_ast(&code).unwrap(); let ast = parser.parse_line_ast(&code).unwrap();
let invocation_info = span_tree::generate::context::CalledMethodInfo { parameters }; let invocation_info =
span_tree::generate::context::CalledMethodInfo { parameters, ..default() };
let ctx = span_tree::generate::MockContext::new_single(ast.id.unwrap(), invocation_info); let ctx = span_tree::generate::MockContext::new_single(ast.id.unwrap(), invocation_info);
let output_span_tree = span_tree::SpanTree::default(); let output_span_tree = span_tree::SpanTree::default();
let input_span_tree = span_tree::SpanTree::new(&ast, &ctx).unwrap(); let input_span_tree = span_tree::SpanTree::new(&ast, &ctx).unwrap();
@ -383,7 +385,8 @@ pub fn expression_mock3() -> Expression {
}; };
let parameters = vec![this_param, param0, param1, param2, param3]; let parameters = vec![this_param, param0, param1, param2, param3];
let ast = parser.parse_line_ast(&code).unwrap(); let ast = parser.parse_line_ast(&code).unwrap();
let invocation_info = span_tree::generate::context::CalledMethodInfo { parameters }; let invocation_info =
span_tree::generate::context::CalledMethodInfo { parameters, ..default() };
let ctx = span_tree::generate::MockContext::new_single(ast.id.unwrap(), invocation_info); let ctx = span_tree::generate::MockContext::new_single(ast.id.unwrap(), invocation_info);
let output_span_tree = span_tree::SpanTree::new(&ast, &ctx).unwrap(); //span_tree::SpanTree::default(); let output_span_tree = span_tree::SpanTree::new(&ast, &ctx).unwrap(); //span_tree::SpanTree::default();
let input_span_tree = span_tree::SpanTree::new(&ast, &ctx).unwrap(); let input_span_tree = span_tree::SpanTree::new(&ast, &ctx).unwrap();
@ -418,7 +421,8 @@ pub fn expression_mock_trim() -> Expression {
}; };
let parameters = vec![this_param, param0, param1]; let parameters = vec![this_param, param0, param1];
let ast = parser.parse_line_ast(&code).unwrap(); let ast = parser.parse_line_ast(&code).unwrap();
let invocation_info = span_tree::generate::context::CalledMethodInfo { parameters }; let invocation_info =
span_tree::generate::context::CalledMethodInfo { parameters, ..default() };
let ctx = span_tree::generate::MockContext::new_single(ast.id.unwrap(), invocation_info); let ctx = span_tree::generate::MockContext::new_single(ast.id.unwrap(), invocation_info);
let output_span_tree = span_tree::SpanTree::default(); let output_span_tree = span_tree::SpanTree::default();
let input_span_tree = span_tree::SpanTree::new(&ast, &ctx).unwrap(); let input_span_tree = span_tree::SpanTree::new(&ast, &ctx).unwrap();

View File

@ -372,7 +372,6 @@ impl Model {
let code = &expression.viz_code; let code = &expression.viz_code;
expression.span_tree.root_ref_mut().dfs_with_layer_data(builder, |mut node, builder| { expression.span_tree.root_ref_mut().dfs_with_layer_data(builder, |mut node, builder| {
let is_parensed = node.is_parensed();
let skip_opr = if SKIP_OPERATIONS { let skip_opr = if SKIP_OPERATIONS {
node.is_operation() && !is_header node.is_operation() && !is_header
} else { } else {
@ -562,7 +561,8 @@ impl Model {
} }
let new_parent_frp = Some(node.frp.output.clone_ref()); let new_parent_frp = Some(node.frp.output.clone_ref());
let new_shift = if !not_a_port { 0 } else { builder.shift + local_char_offset }; let new_shift = if !not_a_port { 0 } else { builder.shift + local_char_offset };
builder.nested(new_parent, new_parent_frp, is_parensed, new_shift) let parenthesized = node.parenthesized();
builder.nested(new_parent, new_parent_frp, parenthesized, new_shift)
}); });
*self.id_crumbs_map.borrow_mut() = id_crumbs_map; *self.id_crumbs_map.borrow_mut() = id_crumbs_map;
*self.widgets_map.borrow_mut() = widgets_map; *self.widgets_map.borrow_mut() = widgets_map;
@ -1166,9 +1166,10 @@ struct CallInfoMap {
/// Information about the call expression, which are derived from the span tree. /// Information about the call expression, which are derived from the span tree.
#[derive(Debug, Default)] #[derive(Debug, Default)]
struct CallInfo { struct CallInfo {
/// The AST ID associated with `This` span of the call expression. /// The AST ID associated with `self` argument span of the call expression.
target_id: Option<ast::Id>, target_id: Option<ast::Id>,
/// The crumbs of last argument span associated with the call expression. /// The crumbs of last argument span associated with the call expression. It can be any
/// argument, including `self`.
last_argument: Option<Crumbs>, last_argument: Option<Crumbs>,
} }
@ -1178,11 +1179,10 @@ impl CallInfoMap {
expression.root_ref().dfs(|node| { expression.root_ref().dfs(|node| {
if let Some(call_id) = node.kind.call_id() { if let Some(call_id) = node.kind.call_id() {
let mut entry = call_info.entry(call_id).or_default(); let mut entry = call_info.entry(call_id).or_default();
if node.kind.is_this() { if entry.target_id.is_none() {
entry.target_id = node.ast_id; entry.target_id = node.ast_id;
} else if node.kind.is_argument() {
entry.last_argument = Some(node.crumbs.clone());
} }
entry.last_argument = Some(node.crumbs.clone());
} }
}); });

View File

@ -454,13 +454,17 @@ impl LazyDropdown {
current_value <- all(set_current_value, &init)._0(); current_value <- all(set_current_value, &init)._0();
dropdown.set_selected_entries <+ current_value.map(|s| s.iter().cloned().collect()); dropdown.set_selected_entries <+ current_value.map(|s| s.iter().cloned().collect());
first_selected_entry <- dropdown.selected_entries.map(|e| e.iter().next().cloned()); first_selected_entry <- dropdown.selected_entries.map(|e| e.iter().next().cloned());
output_value <+ first_selected_entry.on_change();
is_open <- all(is_open, &init)._0(); is_open <- all(is_open, &init)._0();
dropdown.set_open <+ is_open.on_change(); dropdown.set_open <+ is_open.on_change();
// initialize selection before emitting output. We do not want to emit the
// selected value immediately after initialization, without actual user action.
init.emit(());
output_value <+ first_selected_entry.on_change();
} }
init.emit(());
*self = LazyDropdown::Initialized(dropdown, network); *self = LazyDropdown::Initialized(dropdown, network);
} }
} }

View File

@ -205,7 +205,7 @@ type Filter_Condition
Gets a widget set up for a Filter_Condition. Gets a widget set up for a Filter_Condition.
default_widget = default_widget =
names = ["Equal", "Not Equal", "Is In", "Not In", "Is True", "Is False", "Is Nothing", "Not Nothing", "Is Empty", "Not Empty", "Less", "Equal Or Less", "Greater", "Equal Or Greater", "Between", "Starts With", "Ends With", "Contains", "Not Contains", "Like", "Not Like"] names = ["Equal", "Not Equal", "Is In", "Not In", "Is True", "Is False", "Is Nothing", "Not Nothing", "Is Empty", "Not Empty", "Less", "Equal Or Less", "Greater", "Equal Or Greater", "Between", "Starts With", "Ends With", "Contains", "Not Contains", "Like", "Not Like"]
values = ["(Filter_Condition.Equal _)", "(Filter_Condition.Not_Equal _)", "(Filter_Condition.Is_In _)", "(Filter_Condition.Not_In _)", "Filter_Condition.Is_True", "Filter_Condition.Is_False", "Filter_Condition.Is_Nothing", "Filter_Condition.Not_Nothing", "Filter_Condition.Is_Empty", "Filter_Condition.Not_Empty", "(Filter_Condition.Less _)", "(Filter_Condition.Equal_Or_Less _)", "(Filter_Condition.Greater _)", "(Filter_Condition.Equal_Or_Greater _)", "(Filter_Condition.Between _ _)", "(Filter_Condition.Starts_With _)", "(Filter_Condition.Ends_With _)", "(Filter_Condition.Contains _)", "(Filter_Condition.Not_Contains _)", "(Filter_Condition.Like _)", "(Filter_Condition.Not_Like _)"] values = ["(Filter_Condition.Equal)", "(Filter_Condition.Not_Equal)", "(Filter_Condition.Is_In)", "(Filter_Condition.Not_In)", "Filter_Condition.Is_True", "Filter_Condition.Is_False", "Filter_Condition.Is_Nothing", "Filter_Condition.Not_Nothing", "Filter_Condition.Is_Empty", "Filter_Condition.Not_Empty", "(Filter_Condition.Less)", "(Filter_Condition.Equal_Or_Less)", "(Filter_Condition.Greater)", "(Filter_Condition.Equal_Or_Greater)", "(Filter_Condition.Between)", "(Filter_Condition.Starts_With)", "(Filter_Condition.Ends_With)", "(Filter_Condition.Contains)", "(Filter_Condition.Not_Contains)", "(Filter_Condition.Like)", "(Filter_Condition.Not_Like)"]
options = names.zip values . map p-> Option p.first p.second options = names.zip values . map p-> Option p.first p.second
Single_Choice values=options display=Display.Always Single_Choice values=options display=Display.Always