Properly qualify trait methods in qualify_path assist
This commit is contained in:
parent
d983f18df7
commit
bc11475a2a
5 changed files with 117 additions and 69 deletions
|
@ -45,7 +45,7 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
|
||||||
let group = import_group_message(import_assets.import_candidate());
|
let group = import_group_message(import_assets.import_candidate());
|
||||||
let scope = ImportScope::find_insert_use_container(import_assets.syntax_under_caret(), ctx)?;
|
let scope = ImportScope::find_insert_use_container(import_assets.syntax_under_caret(), ctx)?;
|
||||||
let syntax = scope.as_syntax_node();
|
let syntax = scope.as_syntax_node();
|
||||||
for import in proposed_imports {
|
for (import, _) in proposed_imports {
|
||||||
acc.add_group(
|
acc.add_group(
|
||||||
&group,
|
&group,
|
||||||
AssistId("auto_import", AssistKind::QuickFix),
|
AssistId("auto_import", AssistKind::QuickFix),
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
use std::collections::BTreeSet;
|
use std::iter;
|
||||||
|
|
||||||
use syntax::{ast, AstNode, TextRange};
|
use hir::AsName;
|
||||||
|
use ide_db::RootDatabase;
|
||||||
|
use syntax::{
|
||||||
|
ast,
|
||||||
|
ast::{make, ArgListOwner},
|
||||||
|
AstNode, TextRange,
|
||||||
|
};
|
||||||
use test_utils::mark;
|
use test_utils::mark;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -10,6 +16,8 @@ use crate::{
|
||||||
AssistId, AssistKind, GroupLabel,
|
AssistId, AssistKind, GroupLabel,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ASSIST_ID: AssistId = AssistId("qualify_path", AssistKind::QuickFix);
|
||||||
|
|
||||||
// Assist: qualify_path
|
// Assist: qualify_path
|
||||||
//
|
//
|
||||||
// If the name is unresolved, provides all possible qualified paths for it.
|
// If the name is unresolved, provides all possible qualified paths for it.
|
||||||
|
@ -53,30 +61,14 @@ pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
|
||||||
ImportCandidate::UnqualifiedName(candidate) => {
|
ImportCandidate::UnqualifiedName(candidate) => {
|
||||||
qualify_path_unqualified_name(acc, proposed_imports, range, &candidate.name)
|
qualify_path_unqualified_name(acc, proposed_imports, range, &candidate.name)
|
||||||
}
|
}
|
||||||
ImportCandidate::TraitAssocItem(candidate) => {
|
ImportCandidate::TraitAssocItem(_) => {
|
||||||
let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?;
|
let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?;
|
||||||
let (qualifier, segment) = (path.qualifier()?, path.segment()?);
|
let (qualifier, segment) = (path.qualifier()?, path.segment()?);
|
||||||
qualify_path_trait_assoc_item(
|
qualify_path_trait_assoc_item(acc, proposed_imports, range, qualifier, segment)
|
||||||
acc,
|
|
||||||
proposed_imports,
|
|
||||||
range,
|
|
||||||
qualifier,
|
|
||||||
segment,
|
|
||||||
&candidate.name,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
ImportCandidate::TraitMethod(candidate) => {
|
ImportCandidate::TraitMethod(_) => {
|
||||||
let mcall_expr = ast::MethodCallExpr::cast(import_assets.syntax_under_caret().clone())?;
|
let mcall_expr = ast::MethodCallExpr::cast(import_assets.syntax_under_caret().clone())?;
|
||||||
let receiver = mcall_expr.receiver()?;
|
qualify_path_trait_method(acc, ctx.sema.db, proposed_imports, range, mcall_expr)?;
|
||||||
let name_ref = mcall_expr.name_ref()?;
|
|
||||||
qualify_path_trait_method(
|
|
||||||
acc,
|
|
||||||
proposed_imports,
|
|
||||||
range,
|
|
||||||
receiver,
|
|
||||||
name_ref,
|
|
||||||
&candidate.name,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Some(())
|
Some(())
|
||||||
|
@ -85,17 +77,17 @@ pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
|
||||||
// a test that covers this -> `associated_struct_const`
|
// a test that covers this -> `associated_struct_const`
|
||||||
fn qualify_path_qualifier_start(
|
fn qualify_path_qualifier_start(
|
||||||
acc: &mut Assists,
|
acc: &mut Assists,
|
||||||
proposed_imports: BTreeSet<hir::ModPath>,
|
proposed_imports: Vec<(hir::ModPath, hir::ItemInNs)>,
|
||||||
range: TextRange,
|
range: TextRange,
|
||||||
segment: ast::PathSegment,
|
segment: ast::PathSegment,
|
||||||
qualifier_start: &str,
|
qualifier_start: &ast::NameRef,
|
||||||
) {
|
) {
|
||||||
mark::hit!(qualify_path_qualifier_start);
|
mark::hit!(qualify_path_qualifier_start);
|
||||||
let group_label = GroupLabel(format!("Qualify {}", qualifier_start));
|
let group_label = GroupLabel(format!("Qualify {}", qualifier_start));
|
||||||
for import in proposed_imports {
|
for (import, _) in proposed_imports {
|
||||||
acc.add_group(
|
acc.add_group(
|
||||||
&group_label,
|
&group_label,
|
||||||
AssistId("qualify_path", AssistKind::QuickFix),
|
ASSIST_ID,
|
||||||
format!("Qualify with `{}`", &import),
|
format!("Qualify with `{}`", &import),
|
||||||
range,
|
range,
|
||||||
|builder| {
|
|builder| {
|
||||||
|
@ -109,16 +101,16 @@ fn qualify_path_qualifier_start(
|
||||||
// a test that covers this -> `applicable_when_found_an_import_partial`
|
// a test that covers this -> `applicable_when_found_an_import_partial`
|
||||||
fn qualify_path_unqualified_name(
|
fn qualify_path_unqualified_name(
|
||||||
acc: &mut Assists,
|
acc: &mut Assists,
|
||||||
proposed_imports: BTreeSet<hir::ModPath>,
|
proposed_imports: Vec<(hir::ModPath, hir::ItemInNs)>,
|
||||||
range: TextRange,
|
range: TextRange,
|
||||||
name: &str,
|
name: &ast::NameRef,
|
||||||
) {
|
) {
|
||||||
mark::hit!(qualify_path_unqualified_name);
|
mark::hit!(qualify_path_unqualified_name);
|
||||||
let group_label = GroupLabel(format!("Qualify {}", name));
|
let group_label = GroupLabel(format!("Qualify {}", name));
|
||||||
for import in proposed_imports {
|
for (import, _) in proposed_imports {
|
||||||
acc.add_group(
|
acc.add_group(
|
||||||
&group_label,
|
&group_label,
|
||||||
AssistId("qualify_path", AssistKind::QuickFix),
|
ASSIST_ID,
|
||||||
format!("Qualify as `{}`", &import),
|
format!("Qualify as `{}`", &import),
|
||||||
range,
|
range,
|
||||||
|builder| builder.replace(range, mod_path_to_ast(&import).to_string()),
|
|builder| builder.replace(range, mod_path_to_ast(&import).to_string()),
|
||||||
|
@ -129,18 +121,17 @@ fn qualify_path_unqualified_name(
|
||||||
// a test that covers this -> `associated_trait_const`
|
// a test that covers this -> `associated_trait_const`
|
||||||
fn qualify_path_trait_assoc_item(
|
fn qualify_path_trait_assoc_item(
|
||||||
acc: &mut Assists,
|
acc: &mut Assists,
|
||||||
proposed_imports: BTreeSet<hir::ModPath>,
|
proposed_imports: Vec<(hir::ModPath, hir::ItemInNs)>,
|
||||||
range: TextRange,
|
range: TextRange,
|
||||||
qualifier: ast::Path,
|
qualifier: ast::Path,
|
||||||
segment: ast::PathSegment,
|
segment: ast::PathSegment,
|
||||||
trait_assoc_item_name: &str,
|
|
||||||
) {
|
) {
|
||||||
mark::hit!(qualify_path_trait_assoc_item);
|
mark::hit!(qualify_path_trait_assoc_item);
|
||||||
let group_label = GroupLabel(format!("Qualify {}", trait_assoc_item_name));
|
let group_label = GroupLabel(format!("Qualify {}", &segment));
|
||||||
for import in proposed_imports {
|
for (import, _) in proposed_imports {
|
||||||
acc.add_group(
|
acc.add_group(
|
||||||
&group_label,
|
&group_label,
|
||||||
AssistId("qualify_path", AssistKind::QuickFix),
|
ASSIST_ID,
|
||||||
format!("Qualify with cast as `{}`", &import),
|
format!("Qualify with cast as `{}`", &import),
|
||||||
range,
|
range,
|
||||||
|builder| {
|
|builder| {
|
||||||
|
@ -154,33 +145,74 @@ fn qualify_path_trait_assoc_item(
|
||||||
// a test that covers this -> `trait_method`
|
// a test that covers this -> `trait_method`
|
||||||
fn qualify_path_trait_method(
|
fn qualify_path_trait_method(
|
||||||
acc: &mut Assists,
|
acc: &mut Assists,
|
||||||
proposed_imports: BTreeSet<hir::ModPath>,
|
db: &RootDatabase,
|
||||||
|
proposed_imports: Vec<(hir::ModPath, hir::ItemInNs)>,
|
||||||
range: TextRange,
|
range: TextRange,
|
||||||
receiver: ast::Expr,
|
mcall_expr: ast::MethodCallExpr,
|
||||||
name_ref: ast::NameRef,
|
) -> Option<()> {
|
||||||
trait_method_name: &str,
|
|
||||||
) {
|
|
||||||
mark::hit!(qualify_path_trait_method);
|
mark::hit!(qualify_path_trait_method);
|
||||||
|
|
||||||
|
let receiver = mcall_expr.receiver()?;
|
||||||
|
let trait_method_name = mcall_expr.name_ref()?;
|
||||||
|
let arg_list = mcall_expr.arg_list().map(|arg_list| arg_list.args());
|
||||||
let group_label = GroupLabel(format!("Qualify {}", trait_method_name));
|
let group_label = GroupLabel(format!("Qualify {}", trait_method_name));
|
||||||
for import in proposed_imports {
|
let find_method = |item: &hir::AssocItem| {
|
||||||
|
item.name(db).map(|name| name == trait_method_name.as_name()).unwrap_or(false)
|
||||||
|
};
|
||||||
|
for (import, trait_) in proposed_imports.into_iter().filter_map(filter_trait) {
|
||||||
acc.add_group(
|
acc.add_group(
|
||||||
&group_label,
|
&group_label,
|
||||||
AssistId("qualify_path", AssistKind::QuickFix), // < Does this still count as quickfix?
|
ASSIST_ID,
|
||||||
format!("Qualify `{}`", &import),
|
format!("Qualify `{}`", &import),
|
||||||
range,
|
range,
|
||||||
|builder| {
|
|builder| {
|
||||||
let import = mod_path_to_ast(&import);
|
let import = mod_path_to_ast(&import);
|
||||||
// TODO: check the receiver self type and emit refs accordingly, don't discard other function parameters
|
if let Some(hir::AssocItem::Function(method)) =
|
||||||
builder.replace(range, format!("{}::{}(&{})", import, name_ref, receiver));
|
trait_.items(db).into_iter().find(find_method)
|
||||||
|
{
|
||||||
|
if let Some(self_access) = method.self_param(db).map(|sp| sp.access(db)) {
|
||||||
|
let receiver = receiver.clone();
|
||||||
|
let receiver = match self_access {
|
||||||
|
hir::Access::Shared => make::expr_ref(receiver, false),
|
||||||
|
hir::Access::Exclusive => make::expr_ref(receiver, true),
|
||||||
|
hir::Access::Owned => receiver,
|
||||||
|
};
|
||||||
|
builder.replace(
|
||||||
|
range,
|
||||||
|
format!(
|
||||||
|
"{}::{}{}",
|
||||||
|
import,
|
||||||
|
trait_method_name,
|
||||||
|
match arg_list.clone() {
|
||||||
|
Some(args) => make::arg_list(iter::once(receiver).chain(args)),
|
||||||
|
None => make::arg_list(iter::once(receiver)),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn filter_trait(
|
||||||
|
(import, trait_): (hir::ModPath, hir::ItemInNs),
|
||||||
|
) -> Option<(hir::ModPath, hir::Trait)> {
|
||||||
|
if let hir::ModuleDef::Trait(trait_) = hir::ModuleDef::from(trait_.as_module_def_id()?) {
|
||||||
|
Some((import, trait_))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
|
||||||
use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
|
use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn applicable_when_found_an_import_partial() {
|
fn applicable_when_found_an_import_partial() {
|
||||||
mark::check!(qualify_path_unqualified_name);
|
mark::check!(qualify_path_unqualified_name);
|
||||||
|
|
|
@ -712,6 +712,25 @@ fn handle(action: Action) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn doctest_qualify_path() {
|
||||||
|
check_doc_test(
|
||||||
|
"qualify_path",
|
||||||
|
r#####"
|
||||||
|
fn main() {
|
||||||
|
let map = HashMap<|>::new();
|
||||||
|
}
|
||||||
|
pub mod std { pub mod collections { pub struct HashMap { } } }
|
||||||
|
"#####,
|
||||||
|
r#####"
|
||||||
|
fn main() {
|
||||||
|
let map = std::collections::HashMap::new();
|
||||||
|
}
|
||||||
|
pub mod std { pub mod collections { pub struct HashMap { } } }
|
||||||
|
"#####,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn doctest_remove_dbg() {
|
fn doctest_remove_dbg() {
|
||||||
check_doc_test(
|
check_doc_test(
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
//! Look up accessible paths for items.
|
//! Look up accessible paths for items.
|
||||||
use std::collections::BTreeSet;
|
|
||||||
|
|
||||||
use either::Either;
|
use either::Either;
|
||||||
use hir::{AsAssocItem, AssocItemContainer, ModuleDef, Semantics};
|
use hir::{AsAssocItem, AssocItemContainer, ModuleDef, Semantics};
|
||||||
use ide_db::{imports_locator, RootDatabase};
|
use ide_db::{imports_locator, RootDatabase};
|
||||||
|
@ -29,12 +27,12 @@ pub(crate) enum ImportCandidate {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct TraitImportCandidate {
|
pub(crate) struct TraitImportCandidate {
|
||||||
pub ty: hir::Type,
|
pub ty: hir::Type,
|
||||||
pub name: String,
|
pub name: ast::NameRef,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct PathImportCandidate {
|
pub(crate) struct PathImportCandidate {
|
||||||
pub name: String,
|
pub name: ast::NameRef,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -86,9 +84,9 @@ impl ImportAssets {
|
||||||
fn get_search_query(&self) -> &str {
|
fn get_search_query(&self) -> &str {
|
||||||
match &self.import_candidate {
|
match &self.import_candidate {
|
||||||
ImportCandidate::UnqualifiedName(candidate)
|
ImportCandidate::UnqualifiedName(candidate)
|
||||||
| ImportCandidate::QualifierStart(candidate) => &candidate.name,
|
| ImportCandidate::QualifierStart(candidate) => candidate.name.text(),
|
||||||
ImportCandidate::TraitAssocItem(candidate)
|
ImportCandidate::TraitAssocItem(candidate)
|
||||||
| ImportCandidate::TraitMethod(candidate) => &candidate.name,
|
| ImportCandidate::TraitMethod(candidate) => candidate.name.text(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,7 +94,7 @@ impl ImportAssets {
|
||||||
&self,
|
&self,
|
||||||
sema: &Semantics<RootDatabase>,
|
sema: &Semantics<RootDatabase>,
|
||||||
config: &InsertUseConfig,
|
config: &InsertUseConfig,
|
||||||
) -> BTreeSet<hir::ModPath> {
|
) -> Vec<(hir::ModPath, hir::ItemInNs)> {
|
||||||
let _p = profile::span("import_assists::search_for_imports");
|
let _p = profile::span("import_assists::search_for_imports");
|
||||||
self.search_for(sema, Some(config.prefix_kind))
|
self.search_for(sema, Some(config.prefix_kind))
|
||||||
}
|
}
|
||||||
|
@ -106,7 +104,7 @@ impl ImportAssets {
|
||||||
pub(crate) fn search_for_relative_paths(
|
pub(crate) fn search_for_relative_paths(
|
||||||
&self,
|
&self,
|
||||||
sema: &Semantics<RootDatabase>,
|
sema: &Semantics<RootDatabase>,
|
||||||
) -> BTreeSet<hir::ModPath> {
|
) -> Vec<(hir::ModPath, hir::ItemInNs)> {
|
||||||
let _p = profile::span("import_assists::search_for_relative_paths");
|
let _p = profile::span("import_assists::search_for_relative_paths");
|
||||||
self.search_for(sema, None)
|
self.search_for(sema, None)
|
||||||
}
|
}
|
||||||
|
@ -115,7 +113,7 @@ impl ImportAssets {
|
||||||
&self,
|
&self,
|
||||||
sema: &Semantics<RootDatabase>,
|
sema: &Semantics<RootDatabase>,
|
||||||
prefixed: Option<hir::PrefixKind>,
|
prefixed: Option<hir::PrefixKind>,
|
||||||
) -> BTreeSet<hir::ModPath> {
|
) -> Vec<(hir::ModPath, hir::ItemInNs)> {
|
||||||
let db = sema.db;
|
let db = sema.db;
|
||||||
let mut trait_candidates = FxHashSet::default();
|
let mut trait_candidates = FxHashSet::default();
|
||||||
let current_crate = self.module_with_name_to_import.krate();
|
let current_crate = self.module_with_name_to_import.krate();
|
||||||
|
@ -181,7 +179,7 @@ impl ImportAssets {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
imports_locator::find_imports(sema, current_crate, &self.get_search_query())
|
let mut res = imports_locator::find_imports(sema, current_crate, &self.get_search_query())
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(filter)
|
.filter_map(filter)
|
||||||
.filter_map(|candidate| {
|
.filter_map(|candidate| {
|
||||||
|
@ -191,10 +189,13 @@ impl ImportAssets {
|
||||||
} else {
|
} else {
|
||||||
self.module_with_name_to_import.find_use_path(db, item)
|
self.module_with_name_to_import.find_use_path(db, item)
|
||||||
}
|
}
|
||||||
|
.map(|path| (path, item))
|
||||||
})
|
})
|
||||||
.filter(|use_path| !use_path.segments.is_empty())
|
.filter(|(use_path, _)| !use_path.segments.is_empty())
|
||||||
.take(20)
|
.take(20)
|
||||||
.collect::<BTreeSet<_>>()
|
.collect::<Vec<_>>();
|
||||||
|
res.sort_by_key(|(path, _)| path.clone());
|
||||||
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assoc_to_trait(assoc: AssocItemContainer) -> Option<hir::Trait> {
|
fn assoc_to_trait(assoc: AssocItemContainer) -> Option<hir::Trait> {
|
||||||
|
@ -215,7 +216,7 @@ impl ImportCandidate {
|
||||||
Some(_) => None,
|
Some(_) => None,
|
||||||
None => Some(Self::TraitMethod(TraitImportCandidate {
|
None => Some(Self::TraitMethod(TraitImportCandidate {
|
||||||
ty: sema.type_of_expr(&method_call.receiver()?)?,
|
ty: sema.type_of_expr(&method_call.receiver()?)?,
|
||||||
name: method_call.name_ref()?.syntax().to_string(),
|
name: method_call.name_ref()?,
|
||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -243,24 +244,17 @@ impl ImportCandidate {
|
||||||
hir::PathResolution::Def(hir::ModuleDef::Adt(assoc_item_path)) => {
|
hir::PathResolution::Def(hir::ModuleDef::Adt(assoc_item_path)) => {
|
||||||
ImportCandidate::TraitAssocItem(TraitImportCandidate {
|
ImportCandidate::TraitAssocItem(TraitImportCandidate {
|
||||||
ty: assoc_item_path.ty(sema.db),
|
ty: assoc_item_path.ty(sema.db),
|
||||||
name: segment.syntax().to_string(),
|
name: segment.name_ref()?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
_ => return None,
|
_ => return None,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ImportCandidate::QualifierStart(PathImportCandidate {
|
ImportCandidate::QualifierStart(PathImportCandidate { name: qualifier_start })
|
||||||
name: qualifier_start.syntax().to_string(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ImportCandidate::UnqualifiedName(PathImportCandidate {
|
ImportCandidate::UnqualifiedName(PathImportCandidate {
|
||||||
name: segment
|
name: segment.syntax().descendants().find_map(ast::NameRef::cast)?,
|
||||||
.syntax()
|
|
||||||
.descendants()
|
|
||||||
.find_map(ast::NameRef::cast)?
|
|
||||||
.syntax()
|
|
||||||
.to_string(),
|
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
Some(candidate)
|
Some(candidate)
|
||||||
|
|
|
@ -172,6 +172,9 @@ pub fn expr_call(f: ast::Expr, arg_list: ast::ArgList) -> ast::Expr {
|
||||||
pub fn expr_method_call(receiver: ast::Expr, method: &str, arg_list: ast::ArgList) -> ast::Expr {
|
pub fn expr_method_call(receiver: ast::Expr, method: &str, arg_list: ast::ArgList) -> ast::Expr {
|
||||||
expr_from_text(&format!("{}.{}{}", receiver, method, arg_list))
|
expr_from_text(&format!("{}.{}{}", receiver, method, arg_list))
|
||||||
}
|
}
|
||||||
|
pub fn expr_ref(expr: ast::Expr, exclusive: bool) -> ast::Expr {
|
||||||
|
expr_from_text(&if exclusive { format!("&mut {}", expr) } else { format!("&{}", expr) })
|
||||||
|
}
|
||||||
fn expr_from_text(text: &str) -> ast::Expr {
|
fn expr_from_text(text: &str) -> ast::Expr {
|
||||||
ast_from_text(&format!("const C: () = {};", text))
|
ast_from_text(&format!("const C: () = {};", text))
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue