From ccdfaca584a77f6c9bb0fd79c75654049b9913ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Grabarz?= Date: Mon, 20 Feb 2023 07:39:39 +0100 Subject: [PATCH] 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) image Widgets and placeholders are handled for all chained methods within a node image The qualified method call convention and static methods no longer confuse the argument placeholders image Type constructor expressions now receive placeholder arguments. The placeholders work on nested expressions within a node. image --- CHANGELOG.md | 4 + app/gui/language/ast/impl/src/opr.rs | 12 +- app/gui/language/span-tree/example/src/lib.rs | 12 +- app/gui/language/span-tree/src/action.rs | 4 +- app/gui/language/span-tree/src/generate.rs | 275 +++++++++++------- .../span-tree/src/generate/context.rs | 19 +- app/gui/language/span-tree/src/iter.rs | 19 +- app/gui/language/span-tree/src/node.rs | 32 +- app/gui/language/span-tree/src/node/kind.rs | 46 ++- app/gui/src/controller/graph.rs | 2 +- app/gui/src/controller/graph/executed.rs | 17 +- app/gui/src/controller/graph/widget.rs | 3 +- app/gui/suggestion-database/src/entry.rs | 12 +- app/gui/suggestion-database/src/lib.rs | 6 +- app/gui/view/debug_scene/interface/src/lib.rs | 12 +- .../src/component/node/input/area.rs | 14 +- .../src/component/node/input/widget.rs | 8 +- .../0.0.0-dev/src/Data/Filter_Condition.enso | 2 +- 18 files changed, 302 insertions(+), 197 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36306694fc..1709d8148c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -95,6 +95,9 @@ - [Improved component browser entry filtering and sorting][5645]. The component browser will now provide suggestions matching either the component's label or 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) @@ -480,6 +483,7 @@ [4072]: https://github.com/enso-org/enso/pull/4072 [5645]: https://github.com/enso-org/enso/pull/5645 [5646]: https://github.com/enso-org/enso/pull/5646 +[5656]: https://github.com/enso-org/enso/pull/5656 #### Enso Compiler diff --git a/app/gui/language/ast/impl/src/opr.rs b/app/gui/language/ast/impl/src/opr.rs index 0342baea19..4e6f073fce 100644 --- a/app/gui/language/ast/impl/src/opr.rs +++ b/app/gui/language/ast/impl/src/opr.rs @@ -294,9 +294,19 @@ impl GeneralizedInfix { 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 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 { Some(target_infix) if target_infix.arg.name() == self.name() => diff --git a/app/gui/language/span-tree/example/src/lib.rs b/app/gui/language/span-tree/example/src/lib.rs index 71230c7ea1..57452b5bdc 100644 --- a/app/gui/language/span-tree/example/src/lib.rs +++ b/app/gui/language/span-tree/example/src/lib.rs @@ -26,8 +26,8 @@ pub fn main() { let parens_cr1 = ast::crumbs::MatchCrumb::Segs { val: val.clone(), index: 0 }; let parens_cr = ast::crumbs::MatchCrumb::Segs { val, index: 0 }; let _input_span_tree = builder::TreeBuilder::<()>::new(36) - .add_child(0, 14, node::Kind::Chained, PrefixCrumb::Func) - .add_child(0, 9, node::Kind::operation(), PrefixCrumb::Func) + .add_child(0, 14, node::Kind::chained(), PrefixCrumb::Func) + .add_child(0, 9, node::Kind::Operation, PrefixCrumb::Func) .set_ast_id(Uuid::new_v4()) .done() .add_empty_child(10, InsertionPointType::BeforeTarget) @@ -41,7 +41,7 @@ pub fn main() { .set_ast_id(Uuid::new_v4()) .add_child(1, 19, node::Kind::argument(), parens_cr1) .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()) .done() .add_empty_child(13, InsertionPointType::BeforeTarget) @@ -57,11 +57,11 @@ pub fn main() { let input_span_tree2 = Node::<()>::new() .new_child(|t| { t.new_ast_id() - .kind(node::Kind::Chained) + .kind(node::Kind::chained()) .crumbs(PrefixCrumb::Func) .new_child(|t| { t.size(9.bytes()) - .kind(node::Kind::operation()) + .kind(node::Kind::Operation) .crumbs(PrefixCrumb::Func) .new_ast_id() }) @@ -85,7 +85,7 @@ pub fn main() { .crumbs(parens_cr) .new_child(|t| { t.size(12.bytes()) - .kind(node::Kind::operation()) + .kind(node::Kind::Operation) .crumbs(PrefixCrumb::Func) .new_ast_id() }) diff --git a/app/gui/language/span-tree/src/action.rs b/app/gui/language/span-tree/src/action.rs index 61e1b85510..456dc06959 100644 --- a/app/gui/language/span-tree/src/action.rs +++ b/app/gui/language/span-tree/src/action.rs @@ -390,7 +390,7 @@ mod test { // 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`. 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_empty_child(7, ExpectedArgument(1)) .add_empty_child(7, ExpectedArgument(2)) @@ -416,7 +416,7 @@ mod test { // parameters. We can try setting each of 2 arguments to `baz`. let tree: SpanTree = TreeBuilder::new(10) .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_empty_child(10, ExpectedArgument(0)) .add_empty_child(10, ExpectedArgument(1)) diff --git a/app/gui/language/span-tree/src/generate.rs b/app/gui/language/span-tree/src/generate.rs index 17839a53de..146df8050d 100644 --- a/app/gui/language/span-tree/src/generate.rs +++ b/app/gui/language/span-tree/src/generate.rs @@ -193,10 +193,10 @@ impl<'a> ApplicationBase<'a> { ApplicationBase { function_name, call_id, uses_method_notation: false } } - /// Get the list of method prefix arguments expected by this application. Does not include - /// `self` parameter when using method notation. Returns `None` when the call info is not - /// present in the context or AST doesn't contain enough information to query it. - fn known_prefix_arguments(&self, context: &impl Context) -> Option> { + /// Get the list of method arguments expected by this application. Does not include `self` + /// parameter when using method notation. Returns `None` when the call info is not present in + /// the context or AST doesn't contain enough information to query it. + fn known_arguments(&self, context: &impl Context) -> Option> { // 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 // relevant. @@ -205,17 +205,28 @@ impl<'a> ApplicationBase<'a> { } 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 mut deque: VecDeque = 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 - // the list of expected prefix arguments. - if self.uses_method_notation { + // When a method notation is used on non-static invocation, the first `self` argument is + // already present the access chain. Remove it from the list of expected prefix arguments. + if self.uses_method_notation && has_self_argument && self_in_access { deque.pop_front(); } 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) { + self.call_id = new_call_id; + } + fn into_owned(self) -> ApplicationBase<'static> { let function_name = self.function_name.map(|s| s.into_owned().into()); ApplicationBase { function_name, ..self } @@ -241,30 +252,7 @@ fn generate_node_for_ast( context: &impl Context, ) -> FallibleResult> { if let Some(infix) = GeneralizedInfix::try_new(ast) { - // Code like `ast.func` or `a+b+c`. - 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) - } + infix.generate_node(kind, context) } else { match ast.shape() { ast::Shape::Prefix(_) => @@ -274,13 +262,11 @@ fn generate_node_for_ast( _ => { let size = (ast.len().value as i32).byte_diff(); let ast_id = ast.id; - let children = default(); let name = ast::identifier::name(ast); - let payload = default(); if let Some(info) = ast.id.and_then(|id| context.call_info(id, name)) { let node = { - let kind = node::Kind::operation().with_call_id(ast.id).into(); - Node { kind, size, children, ast_id, payload } + let kind = node::Kind::Operation; + Node { kind, size, ast_id, ..default() } }; // 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 @@ -290,7 +276,7 @@ fn generate_node_for_ast( let params = info.parameters.into_iter(); Ok(generate_expected_arguments(node, kind, provided_prefix_arg_count, params)) } 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( // === Operators (Sections and Infixes) === -impl SpanTreeGenerator for ast::opr::Chain { +impl SpanTreeGenerator for GeneralizedInfix { fn generate_node( &self, kind: impl Into, context: &impl Context, ) -> FallibleResult> { - 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( - this: &ast::opr::Chain, + this: ast::opr::Chain, kind: node::Kind, + mut app_base: ApplicationBase, context: &impl Context, ) -> FallibleResult> { - 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 // (target and two arguments). let removable = this.args.len() >= 2; let node_and_offset: FallibleResult<(Node, usize)> = match &this.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)?; Ok((node, target.offset)) } @@ -349,21 +359,55 @@ fn generate_node_for_opr_chain( if has_target { 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 { gen.generate_empty_node(InsertionPointType::AfterTarget); } 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 { let arg_crumbs = elem.crumb_to_operand(has_left); let arg_ast = Located::new(arg_crumbs, operand.arg.clone_ref()); gen.spacing(operand.offset); - let arg_call_id = if is_access { None } else { elem.infix_id }; - let arg = node::Kind::argument().with_removable(removable).with_call_id(arg_call_id); - gen.generate_ast_node(arg_ast, arg, context)?; + let argument_kind = node::Kind::argument().with_removable(removable); + let argument = gen.generate_ast_node(arg_ast, argument_kind, context)?; + + if let Some(info) = infix_right_argument_info { + argument.node.set_argument_info(info); + } } + gen.generate_empty_node(InsertionPointType::Append); if this.operator.right_assoc { @@ -372,11 +416,12 @@ fn generate_node_for_opr_chain( Ok(( Node { - kind: if is_last { kind.clone() } else { node::Kind::Chained }, - size: gen.current_offset, - children: gen.children, - ast_id: elem.infix_id, - payload: default(), + kind: if is_last { kind.clone() } else { node::Kind::chained().into() }, + parenthesized: false, + size: gen.current_offset, + children: gen.children, + ast_id: elem.infix_id, + payload: default(), }, elem.offset, )) @@ -403,7 +448,7 @@ fn generate_node_for_prefix_chain( context: &impl Context, ) -> FallibleResult> { 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 known_args = known_params.is_some(); let mut known_params = known_params.unwrap_or_default(); @@ -413,8 +458,17 @@ fn generate_node_for_prefix_chain( use ast::crumbs::PrefixCrumb::*; // Removing arguments is possible if there at least two of them 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 node = node?; let is_first = i == 0; @@ -441,11 +495,12 @@ fn generate_node_for_prefix_chain( gen.generate_empty_node(InsertionPointType::Append); } Ok(Node { - kind: if is_last { kind.clone() } else { node::Kind::Chained }, - size: gen.current_offset, - children: gen.children, - ast_id: arg.prefix_id, - payload: default(), + kind: if is_last { kind.clone() } else { node::Kind::chained().into() }, + parenthesized: false, + size: gen.current_offset, + children: gen.children, + ast_id: arg.prefix_id, + payload: default(), }) })?; @@ -472,11 +527,12 @@ fn generate_expected_argument( let arg_node = gen.generate_empty_node(InsertionPointType::ExpectedArgument(index)); arg_node.node.set_argument_info(argument_info); Node { - kind: if is_last { kind } else { node::Kind::Chained }, - size: gen.current_offset, - children: gen.children, - ast_id: None, - payload: default(), + kind: if is_last { kind } else { node::Kind::chained().into() }, + parenthesized: false, + size: gen.current_offset, + children: gen.children, + ast_id: None, + payload: default(), } } @@ -501,14 +557,11 @@ fn generate_expected_arguments( fn tree_generate_node( tree: &ast::Tree, - kind: impl Into, + kind: node::Kind, context: &impl Context, ast_id: Option, ) -> FallibleResult> { - let kind = match &tree.type_info { - ast::TreeType::Group => node::Kind::Group, - _ => kind.into(), - }; + let parenthesized = matches!(tree.type_info, ast::TreeType::Group); let mut children = vec![]; let size; if let Some(leaf_info) = &tree.leaf_info { @@ -527,13 +580,7 @@ fn tree_generate_node( offset += size; } SpanSeed::Child(ast::SpanSeedChild { node }) => { - let kind = node::Kind::Argument(node::Argument { - removable: false, - name: None, - tp: None, - call_id: None, - tag_values: vec![], - }); + let kind = node::Kind::argument(); let node = node.generate_node(kind, context)?; let child_size = node.size; let ast_crumbs = vec![ast::crumbs::TreeCrumb { index }.into()]; @@ -545,7 +592,7 @@ fn tree_generate_node( size = offset; } 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_leaf(0, 1, node::Kind::this(), InfixCrumb::LeftOperand) .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_leaf(0, 3, node::Kind::operation(), PrefixCrumb::Func) + .add_leaf(0, 3, node::Kind::Operation, PrefixCrumb::Func) .add_empty_child(4, BeforeTarget) .add_leaf(4, 3, node::Kind::this(), PrefixCrumb::Arg) .add_empty_child(7, Append) @@ -665,7 +712,7 @@ mod test { .add_empty_child(11, Append) .done() .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_empty_child(15, Append) .build(); @@ -682,20 +729,20 @@ mod test { clear_parameter_infos(&mut tree.root); let expected = TreeBuilder::new(26) - .add_child(0, 22, node::Kind::Chained, InfixCrumb::LeftOperand) - .add_child(0, 5, node::Kind::Chained, InfixCrumb::LeftOperand) + .add_child(0, 22, node::Kind::chained(), InfixCrumb::LeftOperand) + .add_child(0, 5, node::Kind::chained(), InfixCrumb::LeftOperand) .add_empty_child(0, BeforeTarget) .add_leaf(0, 1, node::Kind::this().removable(), InfixCrumb::LeftOperand) .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_empty_child(5, Append) .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(0, 11, node::Kind::Chained, PrefixCrumb::Func) - .add_child(0, 7, node::Kind::Chained, PrefixCrumb::Func) - .add_leaf(0, 3, node::Kind::operation(), PrefixCrumb::Func) + .add_child(0, 11, node::Kind::chained(), PrefixCrumb::Func) + .add_child(0, 7, node::Kind::chained(), PrefixCrumb::Func) + .add_leaf(0, 3, node::Kind::Operation, PrefixCrumb::Func) .add_empty_child(4, BeforeTarget) .add_leaf(4, 3, node::Kind::this().removable(), PrefixCrumb::Arg) .add_empty_child(7, Append) @@ -708,7 +755,7 @@ mod test { .done() .add_empty_child(22, Append) .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_empty_child(26, Append) .build(); @@ -727,11 +774,11 @@ mod test { let expected = TreeBuilder::new(7) .add_empty_child(0, Append) .add_leaf(0, 1, node::Kind::argument().removable(), InfixCrumb::LeftOperand) - .add_leaf(1, 2, node::Kind::operation(), InfixCrumb::Operator) - .add_child(3, 3, node::Kind::Chained, InfixCrumb::RightOperand) + .add_leaf(1, 2, node::Kind::Operation, InfixCrumb::Operator) + .add_child(3, 3, node::Kind::chained(), InfixCrumb::RightOperand) .add_empty_child(0, Append) .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_leaf(3, 1, node::Kind::this().removable(), InfixCrumb::RightOperand) .add_empty_child(4, BeforeTarget) @@ -750,11 +797,11 @@ mod test { clear_parameter_infos(&mut tree.root); let expected = TreeBuilder::new(5) .add_empty_child(0, Append) - .add_leaf(0, 2, node::Kind::operation(), SectionRightCrumb::Opr) - .add_child(2, 2, node::Kind::Chained, SectionRightCrumb::Arg) + .add_leaf(0, 2, node::Kind::Operation, SectionRightCrumb::Opr) + .add_child(2, 2, node::Kind::chained(), SectionRightCrumb::Arg) .add_empty_child(0, Append) .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) .done() .build(); @@ -770,7 +817,7 @@ mod test { clear_parameter_infos(&mut tree.root); 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_leaf(4, 9, node::Kind::this(), PrefixCrumb::Arg) .add_empty_child(13, Append) @@ -807,7 +854,7 @@ mod test { let mut id_map = IdMap::default(); let call_id = id_map.generate(0..3); 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 mut tree: SpanTree = SpanTree::new(&ast, &ctx).unwrap(); 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()), } 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)) .build(); clear_expression_ids(&mut tree.root); @@ -828,7 +875,7 @@ mod test { let mut id_map = IdMap::default(); let call_id = id_map.generate(0..8); 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 mut tree: SpanTree = SpanTree::new(&ast, &ctx).unwrap(); 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()), } 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) .build(); clear_expression_ids(&mut tree.root); @@ -849,8 +896,10 @@ mod test { let mut id_map = IdMap::default(); let call_id = Some(id_map.generate(0..8)); let ast = parser.parse_line_ast_with_id_map("foo here", id_map).unwrap(); - let invocation_info = - CalledMethodInfo { parameters: vec![this_param(None), param1(None), param2(None)] }; + let invocation_info = CalledMethodInfo { + parameters: vec![this_param(None), param1(None), param2(None)], + ..default() + }; let ctx = MockContext::new_single(ast.id.unwrap(), invocation_info); let mut tree: SpanTree = SpanTree::new(&ast, &ctx).unwrap(); 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()), } let expected = TreeBuilder::new(8) - .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_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(4, 4, node::Kind::this(), PrefixCrumb::Arg) .done() .add_empty_child(8, ExpectedArgument(1)) @@ -880,8 +929,10 @@ mod test { let mut id_map = IdMap::default(); let call_id = Some(id_map.generate(0..8)); let ast = parser.parse_line_ast_with_id_map("here.foo", id_map).unwrap(); - let invocation_info = - CalledMethodInfo { parameters: vec![this_param(None), param1(None), param2(None)] }; + let invocation_info = CalledMethodInfo { + parameters: vec![this_param(None), param1(None), param2(None)], + ..default() + }; let ctx = MockContext::new_single(ast.id.unwrap(), invocation_info); let mut tree: SpanTree = SpanTree::new(&ast, &ctx).unwrap(); 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()), } let expected = TreeBuilder::new(8) - .add_child(0, 8, node::Kind::Chained, Crumbs::default()) - .add_child(0, 8, node::Kind::operation(), Crumbs::default()) + .add_child(0, 8, node::Kind::chained(), Crumbs::default()) + .add_child(0, 8, node::Kind::Operation, Crumbs::default()) .add_empty_child(0, BeforeTarget) .add_leaf(0, 4, node::Kind::this(), InfixCrumb::LeftOperand) .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_empty_child(8, Append) .done() diff --git a/app/gui/language/span-tree/src/generate/context.rs b/app/gui/language/span-tree/src/generate/context.rs index af4d17be1d..3e7a3ac321 100644 --- a/app/gui/language/span-tree/src/generate/context.rs +++ b/app/gui/language/span-tree/src/generate/context.rs @@ -11,10 +11,19 @@ use ast::Id; /// 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 { + /// 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, /// Information about arguments taken by a called method. - pub parameters: Vec, + pub parameters: Vec, } impl CalledMethodInfo { @@ -25,6 +34,12 @@ impl CalledMethodInfo { }); 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 + } } diff --git a/app/gui/language/span-tree/src/iter.rs b/app/gui/language/span-tree/src/iter.rs index c50c398f97..52190384c1 100644 --- a/app/gui/language/span-tree/src/iter.rs +++ b/app/gui/language/span-tree/src/iter.rs @@ -94,7 +94,7 @@ impl<'a, T> LeafIterator<'a, T> { fn can_descend(&self, current_node: &Node) -> bool { match &self.fragment { 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::PrefixCrumb::*; use node::Kind; - use node::Kind::*; // Tree we use for tests (C means chained nodes): // root: (-) @@ -131,21 +130,21 @@ mod tests { // gg-children: ()() ()() () 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(4, 1, Kind::operation(), vec![Operator]) + .add_leaf(4, 1, Kind::Operation, vec![Operator]) .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]) .done() .done() - .add_leaf(11, 1, Kind::operation(), vec![Operator]) - .add_child(13, 1, Chained, vec![RightOperand]) + .add_leaf(11, 1, Kind::Operation, vec![Operator]) + .add_child(13, 1, Kind::chained(), vec![RightOperand]) .add_leaf(0, 3, Kind::this(), vec![LeftOperand]) - .add_leaf(4, 1, Kind::operation(), vec![Operator]) - .add_child(6, 5, Chained, vec![RightOperand]) + .add_leaf(4, 1, Kind::Operation, vec![Operator]) + .add_child(6, 5, Kind::chained(), vec![RightOperand]) .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]) .done() .done() diff --git a/app/gui/language/span-tree/src/node.rs b/app/gui/language/span-tree/src/node.rs index 9c2e6d3e08..f34c77eb62 100644 --- a/app/gui/language/span-tree/src/node.rs +++ b/app/gui/language/span-tree/src/node.rs @@ -35,11 +35,12 @@ pub trait Payload = Default + Clone; #[derive(Clone, Debug, Default, Eq, PartialEq)] #[allow(missing_docs)] pub struct Node { - pub kind: Kind, - pub size: ByteDiff, - pub children: Vec>, - pub ast_id: Option, - pub payload: T, + pub kind: Kind, + pub size: ByteDiff, + pub children: Vec>, + pub ast_id: Option, + pub parenthesized: bool, + pub payload: T, } impl Deref for Node { @@ -67,11 +68,12 @@ impl Node { /// Payload mapping utility. pub fn map(self, f: impl Copy + Fn(T) -> S) -> Node { let kind = self.kind; + let parenthesized = self.parenthesized; let size = self.size; let children = self.children.into_iter().map(|t| t.map(f)).collect_vec(); let ast_id = self.ast_id; 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 Node { #[allow(missing_docs)] impl Node { - pub fn is_parensed(&self) -> bool { - self.kind == Kind::Group + pub fn parenthesized(&self) -> bool { + self.parenthesized } pub fn is_root(&self) -> bool { self.kind.is_root() @@ -745,10 +747,10 @@ mod test { let tree: SpanTree = TreeBuilder::new(7) .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_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]) .done() .build(); @@ -804,9 +806,9 @@ mod test { let tree: SpanTree = TreeBuilder::new(7) .add_leaf(0, 1, node::Kind::this(), vec![LeftOperand]) .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_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]) .done() .build(); @@ -835,10 +837,10 @@ mod test { // An example with single call and expected arguments. // See also `generate::test::generating_span_tree_for_unfinished_call` let tree: SpanTree = TreeBuilder::new(8) - .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::chained(), 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(4, 1, node::Kind::operation(), Operator) + .add_leaf(4, 1, node::Kind::Operation, Operator) .add_leaf(5, 3, node::Kind::argument(), RightOperand) .done() .add_empty_child(8, InsertionPointType::ExpectedArgument(0)) diff --git a/app/gui/language/span-tree/src/node/kind.rs b/app/gui/language/span-tree/src/node/kind.rs index 9542ac089e..86a6d1d258 100644 --- a/app/gui/language/span-tree/src/node/kind.rs +++ b/app/gui/language/span-tree/src/node/kind.rs @@ -17,9 +17,9 @@ pub enum Kind { /// A root of the expression tree. Root, /// 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. - Operation(Operation), + Operation, /// A node being a target (or "self") parameter of parent Infix, Section or Prefix. This(This), /// 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 /// `InsertionPoint` between `foo` and `bar` should be set to 3. InsertionPoint(InsertionPoint), - /// A parenthesized expression. - Group, } @@ -40,7 +38,7 @@ pub enum Kind { #[allow(missing_docs)] impl Kind { - pub fn operation() -> Operation { + pub fn chained() -> Chained { default() } pub fn this() -> This { @@ -150,7 +148,7 @@ impl Kind { /// Get the function call AST ID associated with this argument. pub fn call_id(&self) -> Option { match self { - Self::Operation(t) => t.call_id, + Self::Chained(t) => t.call_id, Self::This(t) => t.call_id, Self::Argument(t) => t.call_id, Self::InsertionPoint(t) => t.call_id, @@ -162,7 +160,7 @@ impl Kind { /// or was skipped. pub fn set_argument_info(&mut self, argument_info: ArgumentInfo) -> bool { match self { - Self::Operation(t) => { + Self::Chained(t) => { t.call_id = argument_info.call_id; true } @@ -193,13 +191,12 @@ impl Kind { pub fn variant_name(&self) -> &str { match self { Self::Root => "Root", - Self::Chained => "Chained", - Self::Operation(_) => "Operation", + Self::Chained(_) => "Chained", + Self::Operation => "Operation", Self::This(_) => "This", Self::Argument(_) => "Argument", Self::Token => "Token", 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)] -pub struct Operation { - /// The AST ID of function application that this operation is part of. If this is an access - /// chain operation (e.g. `method.call arg`), the call will be the outermost expression - /// containing all arguments. For other infix operators (not access), the call will be the - /// infix expression containing two arguments. +pub struct Chained { + /// The AST ID of function application that this Chained is a target of. If this is a part of + /// an infix operator chain (e.g. `1 + 2 + 3`) and this chained represents `1 + 2` + /// subexpression, it is effectively a target (`self`) argument of the second `+` operator. + /// In that case the `call_id` will point at its parent `1 + 2 + 3` expression. pub call_id: Option, } // === Setters === -impl Operation { - /// Set operation `call_id` field. See [`Operation::call_id`] for more information. +impl Chained { + /// Set Chained `call_id` field. See [`Chained::call_id`] for more information. pub fn with_call_id(mut self, call_id: Option) -> Self { self.call_id = call_id; self } } -impl From for Kind { - fn from(t: Operation) -> Self { - Self::Operation(t) +impl From for Kind { + fn from(t: Chained) -> Self { + Self::Chained(t) } } diff --git a/app/gui/src/controller/graph.rs b/app/gui/src/controller/graph.rs index 27e0f455d9..4cba48ee7c 100644 --- a/app/gui/src/controller/graph.rs +++ b/app/gui/src/controller/graph.rs @@ -421,7 +421,7 @@ impl EndpointInfo { // Unpleasant. Likely there should be something in span tree that allows obtaining // sequence of nodes between root and given crumb. Or sth. 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 diff --git a/app/gui/src/controller/graph/executed.rs b/app/gui/src/controller/graph/executed.rs index a3a05695a7..a34afc1bf7 100644 --- a/app/gui/src/controller/graph/executed.rs +++ b/app/gui/src/controller/graph/executed.rs @@ -339,8 +339,21 @@ impl Context for Handle { let lookup_registry = || { let info = self.computed_value_info_registry().get(&id)?; let method_call = info.method_call.as_ref()?; - let entry = self.project.suggestion_db().lookup_by_method_pointer(method_call)?; - Some(entry.invocation_info()) + let suggestion_db = self.project.suggestion_db(); + 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); lookup_registry().or_else(fallback) diff --git a/app/gui/src/controller/graph/widget.rs b/app/gui/src/controller/graph/widget.rs index 8dbdc957d5..9f379c0005 100644 --- a/app/gui/src/controller/graph/widget.rs +++ b/app/gui/src/controller/graph/widget.rs @@ -383,10 +383,9 @@ impl QueryData { /// Generate visualization metadata for this query. fn visualization_metadata(&self) -> Metadata { - let relevant_arguments = self.arguments.split_first().map_or_default(|(_self, args)| args); let arguments: Vec = vec![ 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 { diff --git a/app/gui/suggestion-database/src/entry.rs b/app/gui/suggestion-database/src/entry.rs index 21a6f98634..bf66203e54 100644 --- a/app/gui/suggestion-database/src/entry.rs +++ b/app/gui/suggestion-database/src/entry.rs @@ -826,7 +826,8 @@ impl From for Entry { impl TryFrom<&Entry> for language_server::MethodPointer { type Error = failure::Error; fn try_from(entry: &Entry) -> FallibleResult { - (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 defined_on_type = entry.self_type.clone().ok_or_else(missing_this_err)?; Ok(language_server::MethodPointer { @@ -847,7 +848,14 @@ impl TryFrom for language_server::MethodPointer { impl From<&Entry> for 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(); - 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() + } } } diff --git a/app/gui/suggestion-database/src/lib.rs b/app/gui/suggestion-database/src/lib.rs index c7500dd58c..5aa886ef18 100644 --- a/app/gui/suggestion-database/src/lib.rs +++ b/app/gui/suggestion-database/src/lib.rs @@ -399,10 +399,8 @@ impl SuggestionDatabase { &self, method_pointer: &language_server::MethodPointer, ) -> Option> { - self.method_pointer_to_id_map - .borrow() - .get(method_pointer) - .and_then(|id| self.entries.borrow().get(id).cloned()) + let entry_id = self.method_pointer_to_id_map.borrow().get(method_pointer).copied(); + entry_id.and_then(|id| self.entries.borrow().get(&id).cloned()) } /// Get suggestion entry id by method pointer. diff --git a/app/gui/view/debug_scene/interface/src/lib.rs b/app/gui/view/debug_scene/interface/src/lib.rs index 8edf1f8050..59ca7e1a31 100644 --- a/app/gui/view/debug_scene/interface/src/lib.rs +++ b/app/gui/view/debug_scene/interface/src/lib.rs @@ -322,7 +322,8 @@ pub fn expression_mock_string(label: &str) -> Expression { let parser = Parser::new(); let parameters = vec![]; 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 output_span_tree = span_tree::SpanTree::default(); 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 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 output_span_tree = span_tree::SpanTree::default(); 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 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 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(); @@ -418,7 +421,8 @@ pub fn expression_mock_trim() -> Expression { }; let parameters = vec![this_param, param0, param1]; 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 output_span_tree = span_tree::SpanTree::default(); let input_span_tree = span_tree::SpanTree::new(&ast, &ctx).unwrap(); diff --git a/app/gui/view/graph-editor/src/component/node/input/area.rs b/app/gui/view/graph-editor/src/component/node/input/area.rs index 9dd2c69dab..ff1099d4ab 100644 --- a/app/gui/view/graph-editor/src/component/node/input/area.rs +++ b/app/gui/view/graph-editor/src/component/node/input/area.rs @@ -372,7 +372,6 @@ impl Model { let code = &expression.viz_code; 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 { node.is_operation() && !is_header } else { @@ -562,7 +561,8 @@ impl Model { } let new_parent_frp = Some(node.frp.output.clone_ref()); 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.widgets_map.borrow_mut() = widgets_map; @@ -1166,9 +1166,10 @@ struct CallInfoMap { /// Information about the call expression, which are derived from the span tree. #[derive(Debug, Default)] 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, - /// 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, } @@ -1178,11 +1179,10 @@ impl CallInfoMap { expression.root_ref().dfs(|node| { if let Some(call_id) = node.kind.call_id() { 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; - } else if node.kind.is_argument() { - entry.last_argument = Some(node.crumbs.clone()); } + entry.last_argument = Some(node.crumbs.clone()); } }); diff --git a/app/gui/view/graph-editor/src/component/node/input/widget.rs b/app/gui/view/graph-editor/src/component/node/input/widget.rs index e6a25e44bb..d82ada7674 100644 --- a/app/gui/view/graph-editor/src/component/node/input/widget.rs +++ b/app/gui/view/graph-editor/src/component/node/input/widget.rs @@ -454,13 +454,17 @@ impl LazyDropdown { current_value <- all(set_current_value, &init)._0(); dropdown.set_selected_entries <+ current_value.map(|s| s.iter().cloned().collect()); 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(); 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); } } diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Filter_Condition.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Filter_Condition.enso index a5ee851887..1ccfa8143a 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Filter_Condition.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Filter_Condition.enso @@ -205,7 +205,7 @@ type Filter_Condition Gets a widget set up for a Filter_Condition. 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"] - 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 Single_Choice values=options display=Display.Always