Skip to content

Commit

Permalink
Add ArgumentInfo to infix argument (#7530)
Browse files Browse the repository at this point in the history
Adds proper argument information to span tree. Fixes #7107

https://github.com/enso-org/enso/assets/3919101/f7773412-5f0f-46c9-9848-2dc653aee882

# Important Notes
Nodes where the left operand (target) is missing still does not work, because the engine still does not send us any method pointer (perhaps due to its internal design).
  • Loading branch information
farmaazon authored Sep 25, 2023
1 parent df4183e commit d139449
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 11 deletions.
13 changes: 10 additions & 3 deletions app/gui/language/span-tree/src/action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,16 +125,21 @@ impl<'a> Implementation for node::Ref<'a> {
let mut inserted_positional_placeholder_at = None;
let new_ast = modify_preserving_id(ast, |ast| {
if let Some(mut infix) = extended_infix {
let item = ArgWithOffset { arg: new, offset: DEFAULT_OFFSET };
let has_target = infix.target.is_some();
let has_arg = infix.args.last().unwrap().operand.is_some();
let offset = infix
.enumerate_non_empty_operands()
.last()
.map_or(DEFAULT_OFFSET, |op| op.offset);
let last_elem = infix.args.last_mut().unwrap();
let item = ArgWithOffset { arg: new, offset };
let has_target = infix.target.is_some();
last_elem.offset = DEFAULT_OFFSET;
match kind {
ExpectedTarget => infix.target = Some(item),
BeforeArgument(0 | 1) if !has_target => infix.target = Some(item),
BeforeArgument(idx) => infix.insert_operand(*idx, item),
Append if has_arg => infix.push_operand(item),
Append => last_elem.operand = Some(item),
Append | ExpectedOperand => last_elem.operand = Some(item),
ExpectedArgument { .. } => unreachable!(
"Expected arguments should be filtered out before this if block"
),
Expand Down Expand Up @@ -183,6 +188,8 @@ impl<'a> Implementation for node::Ref<'a> {
};
prefix.args.push(item)
}
ExpectedTarget | ExpectedOperand =>
unreachable!("Wrong insertion point in method call"),
}
Ok(prefix.into_ast())
}
Expand Down
91 changes: 87 additions & 4 deletions app/gui/language/span-tree/src/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,12 @@ fn generate_node_for_opr_chain(
let node = target.arg.generate_node(kind, context)?;
Ok((node, target.offset))
}
None => Ok((Node::new().with_kind(InsertionPointType::BeforeArgument(0)), 0)),
None => {
let application_id = this.args.first().and_then(|app| app.infix_id);
let port_id =
application_id.map(|application| PortId::ArgPlaceholder { application, index: 0 });
Ok((Node::new().with_kind(InsertionPointType::ExpectedTarget).with_port_id(port_id), 0))
}
};

// In this fold we pass last generated node and offset after it, wrapped in Result.
Expand All @@ -448,6 +453,7 @@ fn generate_node_for_opr_chain(
let is_first = i == 0;
let is_last = i + 1 == this.args.len();
let has_left = !node.is_insertion_point();
let has_right = elem.operand.is_some();
let opr_crumbs = elem.crumb_to_operator(has_left);
let opr_ast = Located::new(opr_crumbs, elem.operator.ast());
let left_crumbs = if has_left { vec![elem.crumb_to_previous()] } else { vec![] };
Expand Down Expand Up @@ -477,7 +483,7 @@ fn generate_node_for_opr_chain(
}
}

let infix_right_argument_info = if !app_base.uses_method_notation {
let mut infix_right_argument_info = if !app_base.uses_method_notation {
app_base.set_call_id(elem.infix_id);
app_base.resolve(context).and_then(|mut resolved| {
// For resolved infix arguments, the arity should always be 2. First always
Expand Down Expand Up @@ -515,13 +521,21 @@ fn generate_node_for_opr_chain(
};
let argument = gen.generate_ast_node(arg_ast, argument_kind, context)?;

if let Some((index, info)) = infix_right_argument_info {
if let Some((index, info)) = infix_right_argument_info.take() {
argument.node.set_argument_info(info);
argument.node.set_definition_index(index);
}
} else if !app_base.uses_method_notation {
let argument = gen.generate_empty_node(InsertionPointType::ExpectedOperand);
argument.port_id =
elem.infix_id.map(|application| PortId::ArgPlaceholder { application, index: 1 });
if let Some((index, info)) = infix_right_argument_info.take() {
argument.node.set_argument_info(info);
argument.node.set_definition_index(index);
}
}

if is_last && !app_base.uses_method_notation {
if is_last && has_right && !app_base.uses_method_notation {
gen.generate_empty_node(InsertionPointType::Append);
}

Expand Down Expand Up @@ -1387,6 +1401,75 @@ mod test {
.build();
clear_expression_ids(&mut tree.root);
clear_parameter_infos(&mut tree.root);
assert_eq!(tree, expected)
}

#[test]
fn generate_span_tree_for_unfinished_infix() {
let parser = Parser::new();
let this_param = |call_id| ArgumentInfo {
name: Some("self".to_owned()),
tp: Some("Any".to_owned()),
call_id,
..default()
};
let param1 = |call_id| ArgumentInfo {
name: Some("arg1".to_owned()),
tp: Some("Number".to_owned()),
call_id,
..default()
};


// === SectionLeft ===
let mut id_map = IdMap::default();
let call_id = id_map.generate(0..2);
let ast = parser.parse_line_ast_with_id_map("2+", id_map).unwrap();
let invocation_info =
CalledMethodInfo { parameters: vec![this_param(ast.id), param1(ast.id)], ..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() {
[_before, arg0, _opr, arg1] => {
assert_eq!(arg0.argument_info(), Some(&this_param(Some(call_id))));
assert_eq!(arg1.argument_info(), Some(&param1(Some(call_id))));
}
sth_else => panic!("There should be 4 leaves, found: {}", sth_else.len()),
}
let expected = TreeBuilder::new(2)
.add_empty_child(0, BeforeArgument(0))
.add_leaf(0, 1, node::Kind::argument().indexed(0), SectionLeftCrumb::Arg)
.add_leaf(1, 1, node::Kind::Operation, SectionLeftCrumb::Opr)
.add_empty_child(2, ExpectedOperand)
.build();
clear_expression_ids(&mut tree.root);
clear_parameter_infos(&mut tree.root);
assert_eq!(tree, expected);


// === SectionRight ===
let mut id_map = IdMap::default();
let call_id = id_map.generate(0..2);
let ast = parser.parse_line_ast_with_id_map("+2", id_map).unwrap();
let invocation_info =
CalledMethodInfo { parameters: vec![this_param(ast.id), param1(ast.id)], ..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() {
[arg0, _opr, arg1, _append] => {
assert_eq!(arg0.argument_info(), Some(&this_param(Some(call_id))));
assert_eq!(arg1.argument_info(), Some(&param1(Some(call_id))));
}
sth_else => panic!("There should be 4 leaves, found: {}", sth_else.len()),
}
let expected = TreeBuilder::new(2)
.add_empty_child(0, ExpectedTarget)
.add_leaf(0, 1, node::Kind::Operation, SectionRightCrumb::Opr)
.add_leaf(1, 1, node::Kind::argument().indexed(1), SectionRightCrumb::Arg)
.add_empty_child(2, Append)
.build();
clear_expression_ids(&mut tree.root);
clear_parameter_infos(&mut tree.root);
assert_eq!(tree, expected);
}

Expand Down
9 changes: 9 additions & 0 deletions app/gui/language/span-tree/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,18 @@ impl Node {
pub fn is_positional_insertion_point(&self) -> bool {
self.kind.is_positional_insertion_point()
}

pub fn is_expected_argument(&self) -> bool {
self.kind.is_expected_argument()
}

pub fn is_expected_operand(&self) -> bool {
self.kind.is_expected_operand()
}

pub fn is_placeholder(&self) -> bool {
self.is_expected_argument() || self.is_expected_operand()
}
}


Expand Down
17 changes: 16 additions & 1 deletion app/gui/language/span-tree/src/node/kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,14 +99,25 @@ impl Kind {
/// Match the value with `Kind::InsertionPoint{..}` but not
/// `Kind::InsertionPoint(ExpectedArgument(_))`.
pub fn is_positional_insertion_point(&self) -> bool {
self.is_insertion_point() && !self.is_expected_argument()
self.is_insertion_point() && !self.is_expected_argument() && !self.is_expected_operand()
}

/// Match the value with `Kind::InsertionPoint(ExpectedArgument(_))`.
pub fn is_expected_argument(&self) -> bool {
matches!(self, Self::InsertionPoint(t) if t.kind.is_expected_argument())
}

/// Check if given kind is an insertino point for expected operand of an unfinished infix.
pub fn is_expected_operand(&self) -> bool {
matches!(
self,
Self::InsertionPoint(InsertionPoint {
kind: InsertionPointType::ExpectedOperand | InsertionPointType::ExpectedTarget,
..
})
)
}

/// Match the argument in a prefix method application.
pub fn is_prefix_argument(&self) -> bool {
matches!(self, Self::Argument(a) if a.in_prefix_chain)
Expand Down Expand Up @@ -374,6 +385,10 @@ pub enum InsertionPointType {
index: usize,
named: bool,
},
/// Expected target of unfinished infix expression.
ExpectedTarget,
/// Expected operand of unfinished infix expression.
ExpectedOperand,
}

// === Matchers ===
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1651,7 +1651,7 @@ impl<'a> TreeBuilder<'a> {
let ptr_usage = self.pointer_usage.entry(main_ptr).or_default();
let widget_id = main_ptr.to_identity(ptr_usage);

let is_placeholder = span_node.is_expected_argument();
let is_placeholder = span_node.is_expected_argument() || span_node.is_expected_operand();
let sibling_offset = span_node.sibling_offset.as_usize();
let usage_type = span_node.ast_id.and_then(|id| self.usage_type_map.get(&id)).cloned();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ impl SpanWidget for Widget {
}

fn configure(&mut self, _: &Config, ctx: ConfigContext) {
let is_placeholder = ctx.span_node.is_expected_argument();
let is_placeholder = ctx.span_node.is_placeholder();

let expr = ctx.span_expression();
let content = if is_placeholder || ctx.info.connection.is_some() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,7 @@ impl SpanWidget for Widget {
type Config = Config;

fn match_node(ctx: &ConfigContext) -> Score {
let is_placeholder = ctx.span_node.is_expected_argument();
let is_placeholder = ctx.span_node.is_placeholder();
let decl_type = ctx.span_node.kind.tp().map(|t| t.as_str());

let first_decl_is_vector =
Expand Down

0 comments on commit d139449

Please sign in to comment.