fix(els): signature help

This commit is contained in:
Shunsuke Shibayama 2023-08-30 04:13:58 +09:00
parent 1332b009b7
commit b746cd38bd
8 changed files with 148 additions and 56 deletions

View file

@ -543,7 +543,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
.and_then(|(token, expr)| match expr { .and_then(|(token, expr)| match expr {
Expr::Call(call) => { Expr::Call(call) => {
let sig_t = call.obj.t(); let sig_t = call.obj.t();
let nth = self.nth(&uri, call.args.loc(), &token); let nth = self.nth(&uri, &call, pos);
let additional = if matches!(token.kind, Comma) { 1 } else { 0 }; let additional = if matches!(token.kind, Comma) { 1 } else { 0 };
let nth = nth + additional; let nth = nth + additional;
sig_t.non_default_params()?.get(nth).cloned() sig_t.non_default_params()?.get(nth).cloned()

View file

@ -162,6 +162,9 @@ impl FileCache {
pos: Position, pos: Position,
offset: isize, offset: isize,
) -> Option<Token> { ) -> Option<Token> {
if offset == 0 {
return self.get_token(uri, pos);
}
let _ = self.load_once(uri); let _ = self.load_once(uri);
let ent = self.files.borrow_mut(); let ent = self.files.borrow_mut();
let tokens = ent.get(uri)?.token_stream.as_ref()?; let tokens = ent.get(uri)?.token_stream.as_ref()?;

View file

@ -10,6 +10,21 @@ use lsp_types::Position;
use crate::file_cache::FileCache; use crate::file_cache::FileCache;
use crate::util::{self, NormalizedUrl}; use crate::util::{self, NormalizedUrl};
#[derive(Debug)]
pub enum ExprKind {
Call,
Expr,
}
impl ExprKind {
pub const fn matches(&self, expr: &Expr) -> bool {
matches!(
(self, expr),
(ExprKind::Call, Expr::Call(_)) | (ExprKind::Expr, _)
)
}
}
/// This struct provides: /// This struct provides:
/// * namespace where the cursor is located (`get_namespace`) /// * namespace where the cursor is located (`get_namespace`)
/// * cursor(`Token`) -> `Expr` mapping (`get_min_expr`) /// * cursor(`Token`) -> `Expr` mapping (`get_min_expr`)
@ -19,6 +34,7 @@ pub struct HIRVisitor<'a> {
file_cache: &'a FileCache, file_cache: &'a FileCache,
uri: NormalizedUrl, uri: NormalizedUrl,
strict_cmp: bool, strict_cmp: bool,
search: ExprKind,
} }
impl<'a> HIRVisitor<'a> { impl<'a> HIRVisitor<'a> {
@ -32,6 +48,22 @@ impl<'a> HIRVisitor<'a> {
file_cache, file_cache,
uri, uri,
strict_cmp: ERG_MODE, strict_cmp: ERG_MODE,
search: ExprKind::Expr,
}
}
pub fn new_searcher(
hir: MappedRwLockReadGuard<'a, HIR>,
file_cache: &'a FileCache,
uri: NormalizedUrl,
search: ExprKind,
) -> Self {
Self {
hir,
file_cache,
uri,
strict_cmp: ERG_MODE,
search,
} }
} }
@ -176,6 +208,9 @@ impl<'a> HIRVisitor<'a> {
} }
fn return_expr_if_same<'e>(&'e self, expr: &'e Expr, l: &Token, r: &Token) -> Option<&Expr> { fn return_expr_if_same<'e>(&'e self, expr: &'e Expr, l: &Token, r: &Token) -> Option<&Expr> {
if !self.search.matches(expr) {
return None;
}
if self.strict_cmp { if self.strict_cmp {
if l.deep_eq(r) { if l.deep_eq(r) {
Some(expr) Some(expr)
@ -189,6 +224,15 @@ impl<'a> HIRVisitor<'a> {
} }
} }
fn return_expr_if_contains<'e>(
&'e self,
expr: &'e Expr,
token: &Token,
loc: &impl Locational,
) -> Option<&Expr> {
(loc.loc().contains(token.loc()) && self.search.matches(expr)).then_some(expr)
}
fn get_expr<'e>(&'e self, expr: &'e Expr, token: &Token) -> Option<&'e Expr> { fn get_expr<'e>(&'e self, expr: &'e Expr, token: &Token) -> Option<&'e Expr> {
if expr.ln_end() < token.ln_begin() || expr.ln_begin() > token.ln_end() { if expr.ln_end() < token.ln_begin() || expr.ln_begin() > token.ln_end() {
return None; return None;
@ -236,7 +280,7 @@ impl<'a> HIRVisitor<'a> {
bin: &'e BinOp, bin: &'e BinOp,
token: &Token, token: &Token,
) -> Option<&Expr> { ) -> Option<&Expr> {
if &bin.op == token { if &bin.op == token && self.search.matches(expr) {
return Some(expr); return Some(expr);
} }
self.get_expr(&bin.lhs, token) self.get_expr(&bin.lhs, token)
@ -255,13 +299,22 @@ impl<'a> HIRVisitor<'a> {
.as_ref() .as_ref()
.and_then(|(_, end)| self.return_expr_if_same(expr, end, token)) .and_then(|(_, end)| self.return_expr_if_same(expr, end, token))
.or_else(|| { .or_else(|| {
call.attr_name call.attr_name.as_ref().and_then(|attr| {
.as_ref() self.return_expr_if_same(expr, attr.raw.name.token(), token)
.and_then(|attr| self.return_expr_if_same(expr, attr.raw.name.token(), token)) .map(|e| {
crate::_log!("{e}");
e
})
})
}) })
.or_else(|| self.get_expr(&call.obj, token)) .or_else(|| self.get_expr(&call.obj, token))
.or_else(|| self.get_expr_from_args(&call.args, token)) .or_else(|| {
.or_else(|| call.loc().contains(token.loc()).then_some(expr)) self.get_expr_from_args(&call.args, token).map(|e| {
crate::_log!("{:?} / {e}", self.search);
e
})
})
.or_else(|| self.return_expr_if_contains(expr, token, call))
} }
fn get_expr_from_args<'e>(&'e self, args: &'e Args, token: &Token) -> Option<&Expr> { fn get_expr_from_args<'e>(&'e self, args: &'e Args, token: &Token) -> Option<&Expr> {
@ -291,7 +344,7 @@ impl<'a> HIRVisitor<'a> {
) -> Option<&Expr> { ) -> Option<&Expr> {
self.return_expr_if_same(expr, def.sig.ident().raw.name.token(), token) self.return_expr_if_same(expr, def.sig.ident().raw.name.token(), token)
.or_else(|| self.get_expr_from_block(&def.body.block, token)) .or_else(|| self.get_expr_from_block(&def.body.block, token))
.or_else(|| def.loc().contains(token.loc()).then_some(expr)) .or_else(|| self.return_expr_if_contains(expr, token, def))
} }
fn get_expr_from_class_def<'e>( fn get_expr_from_class_def<'e>(
@ -305,7 +358,7 @@ impl<'a> HIRVisitor<'a> {
.as_ref() .as_ref()
.and_then(|req_sup| self.get_expr(req_sup, token)) .and_then(|req_sup| self.get_expr(req_sup, token))
.or_else(|| self.get_expr_from_block(&class_def.methods, token)) .or_else(|| self.get_expr_from_block(&class_def.methods, token))
.or_else(|| class_def.loc().contains(token.loc()).then_some(expr)) .or_else(|| self.return_expr_if_contains(expr, token, class_def))
} }
fn get_expr_from_block<'e>(&'e self, block: &'e Block, token: &Token) -> Option<&Expr> { fn get_expr_from_block<'e>(&'e self, block: &'e Block, token: &Token) -> Option<&Expr> {
@ -345,7 +398,7 @@ impl<'a> HIRVisitor<'a> {
self.return_expr_if_same(expr, patch_def.sig.name().token(), token) self.return_expr_if_same(expr, patch_def.sig.name().token(), token)
.or_else(|| self.get_expr(&patch_def.base, token)) .or_else(|| self.get_expr(&patch_def.base, token))
.or_else(|| self.get_expr_from_block(&patch_def.methods, token)) .or_else(|| self.get_expr_from_block(&patch_def.methods, token))
.or_else(|| patch_def.loc().contains(token.loc()).then_some(expr)) .or_else(|| self.return_expr_if_contains(expr, token, patch_def))
} }
fn get_expr_from_lambda<'e>( fn get_expr_from_lambda<'e>(
@ -354,7 +407,9 @@ impl<'a> HIRVisitor<'a> {
lambda: &'e Lambda, lambda: &'e Lambda,
token: &Token, token: &Token,
) -> Option<&Expr> { ) -> Option<&Expr> {
if util::pos_in_loc(&lambda.params, util::loc_to_pos(token.loc())?) { if util::pos_in_loc(&lambda.params, util::loc_to_pos(token.loc())?)
&& self.search.matches(expr)
{
return Some(expr); return Some(expr);
} }
self.get_expr_from_block(&lambda.body, token) self.get_expr_from_block(&lambda.body, token)
@ -366,7 +421,7 @@ impl<'a> HIRVisitor<'a> {
arr: &'e Array, arr: &'e Array,
token: &Token, token: &Token,
) -> Option<&Expr> { ) -> Option<&Expr> {
if arr.ln_end() == token.ln_end() { if arr.ln_end() == token.ln_end() && self.search.matches(expr) {
// arr: `[1, 2]`, token: `]` // arr: `[1, 2]`, token: `]`
return Some(expr); return Some(expr);
} }
@ -382,7 +437,7 @@ impl<'a> HIRVisitor<'a> {
dict: &'e Dict, dict: &'e Dict,
token: &Token, token: &Token,
) -> Option<&Expr> { ) -> Option<&Expr> {
if dict.ln_end() == token.ln_end() { if dict.ln_end() == token.ln_end() && self.search.matches(expr) {
// arr: `{...}`, token: `}` // arr: `{...}`, token: `}`
return Some(expr); return Some(expr);
} }
@ -408,7 +463,7 @@ impl<'a> HIRVisitor<'a> {
record: &'e Record, record: &'e Record,
token: &Token, token: &Token,
) -> Option<&Expr> { ) -> Option<&Expr> {
if record.ln_end() == token.ln_end() { if record.ln_end() == token.ln_end() && self.search.matches(expr) {
// arr: `{...}`, token: `}` // arr: `{...}`, token: `}`
return Some(expr); return Some(expr);
} }
@ -426,7 +481,7 @@ impl<'a> HIRVisitor<'a> {
set: &'e Set, set: &'e Set,
token: &Token, token: &Token,
) -> Option<&Expr> { ) -> Option<&Expr> {
if set.ln_end() == token.ln_end() { if set.ln_end() == token.ln_end() && self.search.matches(expr) {
// arr: `{...}`, token: `}` // arr: `{...}`, token: `}`
return Some(expr); return Some(expr);
} }
@ -537,18 +592,18 @@ impl<'a> HIRVisitor<'a> {
fn get_args_info(&self, args: &Args, token: &Token) -> Option<VarInfo> { fn get_args_info(&self, args: &Args, token: &Token) -> Option<VarInfo> {
for arg in args.pos_args.iter() { for arg in args.pos_args.iter() {
if let Some(expr) = self.get_expr_info(&arg.expr, token) { if let Some(vi) = self.get_expr_info(&arg.expr, token) {
return Some(expr); return Some(vi);
} }
} }
if let Some(var) = &args.var_args { if let Some(var) = &args.var_args {
if let Some(expr) = self.get_expr_info(&var.expr, token) { if let Some(vi) = self.get_expr_info(&var.expr, token) {
return Some(expr); return Some(vi);
} }
} }
for arg in args.kw_args.iter() { for arg in args.kw_args.iter() {
if let Some(expr) = self.get_expr_info(&arg.expr, token) { if let Some(vi) = self.get_expr_info(&arg.expr, token) {
return Some(expr); return Some(vi);
} }
} }
None None

View file

@ -20,7 +20,7 @@ use erg_compiler::build_hir::HIRBuilder;
use erg_compiler::context::{Context, ModuleContext}; use erg_compiler::context::{Context, ModuleContext};
use erg_compiler::erg_parser::ast::Module; use erg_compiler::erg_parser::ast::Module;
use erg_compiler::erg_parser::parse::{Parsable, SimpleParser}; use erg_compiler::erg_parser::parse::{Parsable, SimpleParser};
use erg_compiler::hir::{Expr, HIR}; use erg_compiler::hir::HIR;
use erg_compiler::lower::ASTLowerer; use erg_compiler::lower::ASTLowerer;
use erg_compiler::module::{SharedCompilerResource, SharedModuleGraph, SharedModuleIndex}; use erg_compiler::module::{SharedCompilerResource, SharedModuleGraph, SharedModuleIndex};
use erg_compiler::ty::HasType; use erg_compiler::ty::HasType;
@ -48,7 +48,7 @@ use serde_json::Value;
use crate::channels::{SendChannels, Sendable, WorkerMessage}; use crate::channels::{SendChannels, Sendable, WorkerMessage};
use crate::completion::CompletionCache; use crate::completion::CompletionCache;
use crate::file_cache::FileCache; use crate::file_cache::FileCache;
use crate::hir_visitor::HIRVisitor; use crate::hir_visitor::{ExprKind, HIRVisitor};
use crate::message::{ErrorMessage, LSPResult, LogMessage, ShowMessage}; use crate::message::{ErrorMessage, LSPResult, LogMessage, ShowMessage};
use crate::util::{self, NormalizedUrl}; use crate::util::{self, NormalizedUrl};
@ -323,7 +323,6 @@ pub struct Server<Checker: BuildRunnable = HIRBuilder, Parser: Parsable = Simple
// TODO: remove modules, analysis_result, and add `shared: SharedCompilerResource` // TODO: remove modules, analysis_result, and add `shared: SharedCompilerResource`
pub(crate) modules: ModuleCache, pub(crate) modules: ModuleCache,
pub(crate) analysis_result: AnalysisResultCache, pub(crate) analysis_result: AnalysisResultCache,
pub(crate) current_sig: Option<Expr>,
pub(crate) channels: Option<SendChannels>, pub(crate) channels: Option<SendChannels>,
pub(crate) _parser: std::marker::PhantomData<fn() -> Parser>, pub(crate) _parser: std::marker::PhantomData<fn() -> Parser>,
pub(crate) _checker: std::marker::PhantomData<fn() -> Checker>, pub(crate) _checker: std::marker::PhantomData<fn() -> Checker>,
@ -343,7 +342,6 @@ impl<C: BuildRunnable, P: Parsable> Clone for Server<C, P> {
comp_cache: self.comp_cache.clone(), comp_cache: self.comp_cache.clone(),
modules: self.modules.clone(), modules: self.modules.clone(),
analysis_result: self.analysis_result.clone(), analysis_result: self.analysis_result.clone(),
current_sig: self.current_sig.clone(),
channels: self.channels.clone(), channels: self.channels.clone(),
_parser: std::marker::PhantomData, _parser: std::marker::PhantomData,
_checker: std::marker::PhantomData, _checker: std::marker::PhantomData,
@ -365,7 +363,6 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
file_cache: FileCache::new(), file_cache: FileCache::new(),
modules: ModuleCache::new(), modules: ModuleCache::new(),
analysis_result: AnalysisResultCache::new(), analysis_result: AnalysisResultCache::new(),
current_sig: None,
channels: None, channels: None,
_parser: std::marker::PhantomData, _parser: std::marker::PhantomData,
_checker: std::marker::PhantomData, _checker: std::marker::PhantomData,
@ -592,7 +589,6 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
self.comp_cache.clear(); self.comp_cache.clear();
self.modules = ModuleCache::new(); self.modules = ModuleCache::new();
self.analysis_result = AnalysisResultCache::new(); self.analysis_result = AnalysisResultCache::new();
self.current_sig = None;
self.channels.as_ref().unwrap().close(); self.channels.as_ref().unwrap().close();
self.start_language_services(); self.start_language_services();
} }
@ -833,6 +829,12 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
.map(|hir| HIRVisitor::new(hir, &self.file_cache, uri.clone())) .map(|hir| HIRVisitor::new(hir, &self.file_cache, uri.clone()))
} }
pub(crate) fn get_searcher(&self, uri: &NormalizedUrl, kind: ExprKind) -> Option<HIRVisitor> {
self.analysis_result
.get_hir(uri)
.map(|hir| HIRVisitor::new_searcher(hir, &self.file_cache, uri.clone(), kind))
}
pub(crate) fn get_local_ctx(&self, uri: &NormalizedUrl, pos: Position) -> Vec<&Context> { pub(crate) fn get_local_ctx(&self, uri: &NormalizedUrl, pos: Position) -> Vec<&Context> {
let mut ctxs = vec![]; let mut ctxs = vec![];
if let Some(mod_ctx) = &self.modules.get(uri) { if let Some(mod_ctx) = &self.modules.get(uri) {

View file

@ -1,8 +1,8 @@
use erg_common::traits::{DequeStream, LimitedDisplay, Locational, NoTypeDisplay}; use erg_common::traits::{DequeStream, LimitedDisplay, Locational, NoTypeDisplay};
use erg_compiler::artifact::BuildRunnable; use erg_compiler::artifact::BuildRunnable;
use erg_compiler::erg_parser::parse::Parsable; use erg_compiler::erg_parser::parse::Parsable;
use erg_compiler::erg_parser::token::{Token, TokenKind}; use erg_compiler::erg_parser::token::{Token, TokenCategory, TokenKind};
use erg_compiler::hir::Expr; use erg_compiler::hir::{Call, Expr};
use erg_compiler::ty::{HasType, ParamTy}; use erg_compiler::ty::{HasType, ParamTy};
use lsp_types::{ use lsp_types::{
@ -10,8 +10,9 @@ use lsp_types::{
SignatureHelpParams, SignatureHelpTriggerKind, SignatureInformation, SignatureHelpParams, SignatureHelpTriggerKind, SignatureInformation,
}; };
use crate::hir_visitor::ExprKind;
use crate::server::{send_log, ELSResult, Server}; use crate::server::{send_log, ELSResult, Server};
use crate::util::NormalizedUrl; use crate::util::{pos_to_loc, NormalizedUrl};
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Trigger { pub enum Trigger {
@ -84,35 +85,58 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
let token = self.file_cache.get_token_relatively(uri, pos, offset)?; let token = self.file_cache.get_token_relatively(uri, pos, offset)?;
crate::_log!("token: {token}"); crate::_log!("token: {token}");
if let Some(visitor) = self.get_visitor(uri) { if let Some(visitor) = self.get_visitor(uri) {
#[allow(clippy::single_match)] if let Some(expr) = visitor.get_min_expr(&token) {
match visitor.get_min_expr(&token) { return Some((token, expr.clone()));
Some(expr) => {
return Some((token, expr.clone()));
}
_ => {}
} }
} }
None None
} }
pub(crate) fn nth( pub(crate) fn get_min_call(
&self, &self,
uri: &NormalizedUrl, uri: &NormalizedUrl,
args_loc: erg_common::error::Location, pos: Position,
token: &Token, offset: isize,
) -> usize { ) -> Option<(Token, Expr)> {
let token = self.file_cache.get_token_relatively(uri, pos, offset)?;
crate::_log!("token: {token}");
if let Some(visitor) = self.get_searcher(uri, ExprKind::Call) {
if let Some(expr) = visitor.get_min_expr(&token) {
return Some((token, expr.clone()));
}
}
None
}
pub(crate) fn nth(&self, uri: &NormalizedUrl, call: &Call, pos: Position) -> usize {
let origin_loc = call
.args
.paren
.as_ref()
.map(|(l, _)| l.loc())
.unwrap_or_else(|| call.obj.loc());
let loc = pos_to_loc(pos);
let tks = self.file_cache.get_token_stream(uri).unwrap_or_default(); let tks = self.file_cache.get_token_stream(uri).unwrap_or_default();
let mut paren = 0usize;
// we should use the latest commas // we should use the latest commas
let commas = tks let commas = tks
.iter() .iter()
.skip_while(|&tk| tk.loc() < args_loc) .skip_while(|&tk| tk.loc() <= origin_loc)
.filter(|tk| tk.is(TokenKind::Comma) && args_loc.ln_end() >= tk.ln_begin()) .filter(|tk| {
// skip `,` of [1, ...]
match tk.category() {
TokenCategory::LEnclosure => {
paren += 1;
}
TokenCategory::REnclosure => {
paren = paren.saturating_sub(1);
}
_ => {}
}
paren == 0 && tk.is(TokenKind::Comma) && tk.loc() <= loc
})
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let argc = commas.len(); commas.len()
commas
.iter()
.position(|c| c.col_end() >= token.col_end())
.unwrap_or(argc) // `commas.col_end() < token.col_end()` means the token is the last argument
} }
fn resend_help( fn resend_help(
@ -123,13 +147,14 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
) -> Option<SignatureHelp> { ) -> Option<SignatureHelp> {
if let Some(token) = self.file_cache.get_token(uri, pos) { if let Some(token) = self.file_cache.get_token(uri, pos) {
crate::_log!("token: {token}"); crate::_log!("token: {token}");
if let Some(Expr::Call(call)) = &self.current_sig { if let Some((_, Expr::Call(call))) = self.get_min_call(uri, pos, 0) {
if call.ln_begin() > token.ln_begin() || call.ln_end() < token.ln_end() { if call.ln_begin() > token.ln_begin() || call.ln_end() < token.ln_end() {
self.current_sig = None;
return None; return None;
} }
let nth = self.nth(uri, call.args.loc(), &token) as u32; let nth = self.nth(uri, &call, pos) as u32;
return self.make_sig_help(call.obj.as_ref(), nth); return self.make_sig_help(call.obj.as_ref(), nth);
} else {
crate::_log!("failed to get the call");
} }
} else { } else {
crate::_log!("failed to get the token"); crate::_log!("failed to get the token");
@ -147,10 +172,9 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
} }
fn get_continuous_help(&mut self, uri: &NormalizedUrl, pos: Position) -> Option<SignatureHelp> { fn get_continuous_help(&mut self, uri: &NormalizedUrl, pos: Position) -> Option<SignatureHelp> {
if let Some((comma, Expr::Call(call))) = self.get_min_expr(uri, pos, -1) { if let Some((_, Expr::Call(call))) = self.get_min_call(uri, pos, -1) {
let nth = self.nth(uri, call.args.loc(), &comma) as u32 + 1; let nth = self.nth(uri, &call, pos) as u32 + 1;
let help = self.make_sig_help(call.obj.as_ref(), nth); let help = self.make_sig_help(call.obj.as_ref(), nth);
self.current_sig = Some(Expr::Call(call));
return help; return help;
} else { } else {
crate::_log!("failed to get continuous help"); crate::_log!("failed to get continuous help");

View file

@ -96,7 +96,7 @@ pub(crate) fn loc_to_pos(loc: erg_common::error::Location) -> Option<Position> {
Some(start) Some(start)
} }
pub fn _pos_to_loc(pos: Position) -> erg_common::error::Location { pub fn pos_to_loc(pos: Position) -> erg_common::error::Location {
erg_common::error::Location::range( erg_common::error::Location::range(
pos.line + 1, pos.line + 1,
pos.character.saturating_sub(1), pos.character.saturating_sub(1),

View file

@ -314,9 +314,9 @@ impl Ord for Location {
} else if self.ln_begin() == self.ln_end() && other.ln_begin() == other.ln_end() { } else if self.ln_begin() == self.ln_end() && other.ln_begin() == other.ln_end() {
// assert_eq!(self.line_begin, other.line_begin); // assert_eq!(self.line_begin, other.line_begin);
// assert_eq!(self.line_end, other.line_end); // assert_eq!(self.line_end, other.line_end);
if self.col_end() < other.col_begin() { if self.col_end() <= other.col_begin() {
Ordering::Less Ordering::Less
} else if other.col_end() < self.col_begin() { } else if other.col_end() <= self.col_begin() {
Ordering::Greater Ordering::Greater
} else { } else {
Ordering::Equal Ordering::Equal

View file

@ -332,6 +332,14 @@ impl Args {
} }
} }
pub fn last(&self) -> Option<&Expr> {
if self.kw_args.is_empty() {
self.pos_args.last().map(|a| &a.expr)
} else {
self.kw_args.last().map(|a| &a.expr)
}
}
pub fn remove_left_or_key(&mut self, key: &str) -> Option<Expr> { pub fn remove_left_or_key(&mut self, key: &str) -> Option<Expr> {
if !self.pos_args.is_empty() { if !self.pos_args.is_empty() {
Some(self.pos_args.remove(0).expr) Some(self.pos_args.remove(0).expr)