Auto merge of #17967 - Veykril:mbe-tests, r=Veykril
internal: Lay basic ground work for standalone mbe tests
Most of our mbe hir-def tests don't actually do anything name res relevant, we can (and should) move those down the stack into `mbe/hir-expand`.
diff --git a/crates/base-db/src/input.rs b/crates/base-db/src/input.rs
index 3616fa9..032e4a8 100644
--- a/crates/base-db/src/input.rs
+++ b/crates/base-db/src/input.rs
@@ -374,37 +374,6 @@
self.arena.alloc(data)
}
- /// Remove the crate from crate graph. If any crates depend on this crate, the dependency would be replaced
- /// with the second input.
- pub fn remove_and_replace(
- &mut self,
- id: CrateId,
- replace_with: CrateId,
- ) -> Result<(), CyclicDependenciesError> {
- for (x, data) in self.arena.iter() {
- if x == id {
- continue;
- }
- for edge in &data.dependencies {
- if edge.crate_id == id {
- self.check_cycle_after_dependency(edge.crate_id, replace_with)?;
- }
- }
- }
- // if everything was ok, start to replace
- for (x, data) in self.arena.iter_mut() {
- if x == id {
- continue;
- }
- for edge in &mut data.dependencies {
- if edge.crate_id == id {
- edge.crate_id = replace_with;
- }
- }
- }
- Ok(())
- }
-
pub fn add_dep(
&mut self,
from: CrateId,
@@ -412,26 +381,17 @@
) -> Result<(), CyclicDependenciesError> {
let _p = tracing::info_span!("add_dep").entered();
- self.check_cycle_after_dependency(from, dep.crate_id)?;
-
- self.arena[from].add_dep(dep);
- Ok(())
- }
-
- /// Check if adding a dep from `from` to `to` creates a cycle. To figure
- /// that out, look for a path in the *opposite* direction, from `to` to
- /// `from`.
- fn check_cycle_after_dependency(
- &self,
- from: CrateId,
- to: CrateId,
- ) -> Result<(), CyclicDependenciesError> {
- if let Some(path) = self.find_path(&mut FxHashSet::default(), to, from) {
+ // Check if adding a dep from `from` to `to` creates a cycle. To figure
+ // that out, look for a path in the *opposite* direction, from `to` to
+ // `from`.
+ if let Some(path) = self.find_path(&mut FxHashSet::default(), dep.crate_id, from) {
let path = path.into_iter().map(|it| (it, self[it].display_name.clone())).collect();
let err = CyclicDependenciesError { path };
- assert!(err.from().0 == from && err.to().0 == to);
+ assert!(err.from().0 == from && err.to().0 == dep.crate_id);
return Err(err);
}
+
+ self.arena[from].add_dep(dep);
Ok(())
}
diff --git a/crates/hir-ty/src/consteval.rs b/crates/hir-ty/src/consteval.rs
index 8b6cde9..e5c493a 100644
--- a/crates/hir-ty/src/consteval.rs
+++ b/crates/hir-ty/src/consteval.rs
@@ -11,7 +11,7 @@
ConstBlockLoc, EnumVariantId, GeneralConstId, StaticId,
};
use hir_expand::Lookup;
-use stdx::never;
+use stdx::{never, IsNoneOr};
use triomphe::Arc;
use crate::{
@@ -184,6 +184,22 @@
}
}
+pub fn try_const_isize(db: &dyn HirDatabase, c: &Const) -> Option<i128> {
+ match &c.data(Interner).value {
+ chalk_ir::ConstValue::BoundVar(_) => None,
+ chalk_ir::ConstValue::InferenceVar(_) => None,
+ chalk_ir::ConstValue::Placeholder(_) => None,
+ chalk_ir::ConstValue::Concrete(c) => match &c.interned {
+ ConstScalar::Bytes(it, _) => Some(i128::from_le_bytes(pad16(it, true))),
+ ConstScalar::UnevaluatedConst(c, subst) => {
+ let ec = db.const_eval(*c, subst.clone(), None).ok()?;
+ try_const_isize(db, &ec)
+ }
+ _ => None,
+ },
+ }
+}
+
pub(crate) fn const_eval_recover(
_: &dyn HirDatabase,
_: &Cycle,
@@ -256,8 +272,8 @@
) -> Result<i128, ConstEvalError> {
let def = variant_id.into();
let body = db.body(def);
+ let loc = variant_id.lookup(db.upcast());
if body.exprs[body.body_expr] == Expr::Missing {
- let loc = variant_id.lookup(db.upcast());
let prev_idx = loc.index.checked_sub(1);
let value = match prev_idx {
Some(prev_idx) => {
@@ -269,13 +285,21 @@
};
return Ok(value);
}
+
+ let repr = db.enum_data(loc.parent).repr;
+ let is_signed = repr.and_then(|repr| repr.int).is_none_or(|int| int.is_signed());
+
let mir_body = db.monomorphized_mir_body(
def,
Substitution::empty(Interner),
db.trait_environment_for_body(def),
)?;
let c = interpret_mir(db, mir_body, false, None).0?;
- let c = try_const_usize(db, &c).unwrap() as i128;
+ let c = if is_signed {
+ try_const_isize(db, &c).unwrap()
+ } else {
+ try_const_usize(db, &c).unwrap() as i128
+ };
Ok(c)
}
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 527ecf0..6328a3c 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -2208,6 +2208,35 @@
db.function_data(self.id).is_async()
}
+ pub fn returns_impl_future(self, db: &dyn HirDatabase) -> bool {
+ if self.is_async(db) {
+ return true;
+ }
+
+ let Some(impl_traits) = self.ret_type(db).as_impl_traits(db) else { return false };
+ let Some(future_trait_id) =
+ db.lang_item(self.ty(db).env.krate, LangItem::Future).and_then(|t| t.as_trait())
+ else {
+ return false;
+ };
+ let Some(sized_trait_id) =
+ db.lang_item(self.ty(db).env.krate, LangItem::Sized).and_then(|t| t.as_trait())
+ else {
+ return false;
+ };
+
+ let mut has_impl_future = false;
+ impl_traits
+ .filter(|t| {
+ let fut = t.id == future_trait_id;
+ has_impl_future |= fut;
+ !fut && t.id != sized_trait_id
+ })
+ // all traits but the future trait must be auto traits
+ .all(|t| t.is_auto(db))
+ && has_impl_future
+ }
+
/// Does this function have `#[test]` attribute?
pub fn is_test(self, db: &dyn HirDatabase) -> bool {
db.function_data(self.id).attrs.is_test()
diff --git a/crates/ide-assists/src/handlers/explicit_enum_discriminant.rs b/crates/ide-assists/src/handlers/explicit_enum_discriminant.rs
new file mode 100644
index 0000000..fafc344
--- /dev/null
+++ b/crates/ide-assists/src/handlers/explicit_enum_discriminant.rs
@@ -0,0 +1,206 @@
+use hir::Semantics;
+use ide_db::{
+ assists::{AssistId, AssistKind},
+ source_change::SourceChangeBuilder,
+ RootDatabase,
+};
+use syntax::{ast, AstNode};
+
+use crate::{AssistContext, Assists};
+
+// Assist: explicit_enum_discriminant
+//
+// Adds explicit discriminant to all enum variants.
+//
+// ```
+// enum TheEnum$0 {
+// Foo,
+// Bar,
+// Baz = 42,
+// Quux,
+// }
+// ```
+// ->
+// ```
+// enum TheEnum {
+// Foo = 0,
+// Bar = 1,
+// Baz = 42,
+// Quux = 43,
+// }
+// ```
+pub(crate) fn explicit_enum_discriminant(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let enum_node = ctx.find_node_at_offset::<ast::Enum>()?;
+ let enum_def = ctx.sema.to_def(&enum_node)?;
+
+ let is_data_carrying = enum_def.is_data_carrying(ctx.db());
+ let has_primitive_repr = enum_def.repr(ctx.db()).and_then(|repr| repr.int).is_some();
+
+ // Data carrying enums without a primitive repr have no stable discriminants.
+ if is_data_carrying && !has_primitive_repr {
+ return None;
+ }
+
+ let variant_list = enum_node.variant_list()?;
+
+ // Don't offer the assist if the enum has no variants or if all variants already have an
+ // explicit discriminant.
+ if variant_list.variants().all(|variant_node| variant_node.expr().is_some()) {
+ return None;
+ }
+
+ acc.add(
+ AssistId("explicit_enum_discriminant", AssistKind::RefactorRewrite),
+ "Add explicit enum discriminants",
+ enum_node.syntax().text_range(),
+ |builder| {
+ for variant_node in variant_list.variants() {
+ add_variant_discriminant(&ctx.sema, builder, &variant_node);
+ }
+ },
+ );
+
+ Some(())
+}
+
+fn add_variant_discriminant(
+ sema: &Semantics<'_, RootDatabase>,
+ builder: &mut SourceChangeBuilder,
+ variant_node: &ast::Variant,
+) {
+ if variant_node.expr().is_some() {
+ return;
+ }
+
+ let Some(variant_def) = sema.to_def(variant_node) else {
+ return;
+ };
+ let Ok(discriminant) = variant_def.eval(sema.db) else {
+ return;
+ };
+
+ let variant_range = variant_node.syntax().text_range();
+
+ builder.insert(variant_range.end(), format!(" = {discriminant}"));
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::explicit_enum_discriminant;
+
+ #[test]
+ fn non_primitive_repr_non_data_bearing_add_discriminant() {
+ check_assist(
+ explicit_enum_discriminant,
+ r#"
+enum TheEnum$0 {
+ Foo,
+ Bar,
+ Baz = 42,
+ Quux,
+ FooBar = -5,
+ FooBaz,
+}
+"#,
+ r#"
+enum TheEnum {
+ Foo = 0,
+ Bar = 1,
+ Baz = 42,
+ Quux = 43,
+ FooBar = -5,
+ FooBaz = -4,
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn primitive_repr_data_bearing_add_discriminant() {
+ check_assist(
+ explicit_enum_discriminant,
+ r#"
+#[repr(u8)]
+$0enum TheEnum {
+ Foo { x: u32 },
+ Bar,
+ Baz(String),
+ Quux,
+}
+"#,
+ r#"
+#[repr(u8)]
+enum TheEnum {
+ Foo { x: u32 } = 0,
+ Bar = 1,
+ Baz(String) = 2,
+ Quux = 3,
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn non_primitive_repr_data_bearing_not_applicable() {
+ check_assist_not_applicable(
+ explicit_enum_discriminant,
+ r#"
+enum TheEnum$0 {
+ Foo,
+ Bar(u16),
+ Baz,
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn primitive_repr_non_data_bearing_add_discriminant() {
+ check_assist(
+ explicit_enum_discriminant,
+ r#"
+#[repr(i64)]
+enum TheEnum {
+ Foo = 1 << 63,
+ Bar,
+ Baz$0 = 0x7fff_ffff_ffff_fffe,
+ Quux,
+}
+"#,
+ r#"
+#[repr(i64)]
+enum TheEnum {
+ Foo = 1 << 63,
+ Bar = -9223372036854775807,
+ Baz = 0x7fff_ffff_ffff_fffe,
+ Quux = 9223372036854775807,
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn discriminants_already_explicit_not_applicable() {
+ check_assist_not_applicable(
+ explicit_enum_discriminant,
+ r#"
+enum TheEnum$0 {
+ Foo = 0,
+ Bar = 4,
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn empty_enum_not_applicable() {
+ check_assist_not_applicable(
+ explicit_enum_discriminant,
+ r#"
+enum TheEnum$0 {}
+"#,
+ );
+ }
+}
diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs
index c88cb3d..b2ccd1f 100644
--- a/crates/ide-assists/src/lib.rs
+++ b/crates/ide-assists/src/lib.rs
@@ -136,6 +136,7 @@
mod destructure_tuple_binding;
mod desugar_doc_comment;
mod expand_glob_import;
+ mod explicit_enum_discriminant;
mod extract_expressions_from_format_string;
mod extract_function;
mod extract_module;
@@ -266,6 +267,7 @@
destructure_tuple_binding::destructure_tuple_binding,
destructure_struct_binding::destructure_struct_binding,
expand_glob_import::expand_glob_import,
+ explicit_enum_discriminant::explicit_enum_discriminant,
extract_expressions_from_format_string::extract_expressions_from_format_string,
extract_struct_from_enum_variant::extract_struct_from_enum_variant,
extract_type_alias::extract_type_alias,
diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs
index dce7bbf..48e12a8 100644
--- a/crates/ide-assists/src/tests/generated.rs
+++ b/crates/ide-assists/src/tests/generated.rs
@@ -910,6 +910,29 @@
}
#[test]
+fn doctest_explicit_enum_discriminant() {
+ check_doc_test(
+ "explicit_enum_discriminant",
+ r#####"
+enum TheEnum$0 {
+ Foo,
+ Bar,
+ Baz = 42,
+ Quux,
+}
+"#####,
+ r#####"
+enum TheEnum {
+ Foo = 0,
+ Bar = 1,
+ Baz = 42,
+ Quux = 43,
+}
+"#####,
+ )
+}
+
+#[test]
fn doctest_extract_expressions_from_format_string() {
check_doc_test(
"extract_expressions_from_format_string",
diff --git a/crates/ide-completion/src/completions/item_list/trait_impl.rs b/crates/ide-completion/src/completions/item_list/trait_impl.rs
index fc6e1eb..e93bb8d 100644
--- a/crates/ide-completion/src/completions/item_list/trait_impl.rs
+++ b/crates/ide-completion/src/completions/item_list/trait_impl.rs
@@ -31,14 +31,14 @@
//! }
//! ```
-use hir::HasAttrs;
+use hir::{HasAttrs, Name};
use ide_db::{
documentation::HasDocs, path_transform::PathTransform,
syntax_helpers::insert_whitespace_into_node, traits::get_missing_assoc_items, SymbolKind,
};
use syntax::{
- ast::{self, edit_in_place::AttrsOwnerEdit, HasTypeBounds},
- format_smolstr, AstNode, SmolStr, SyntaxElement, SyntaxKind, TextRange, ToSmolStr, T,
+ ast::{self, edit_in_place::AttrsOwnerEdit, make, HasGenericArgs, HasTypeBounds},
+ format_smolstr, ted, AstNode, SmolStr, SyntaxElement, SyntaxKind, TextRange, ToSmolStr, T,
};
use text_edit::TextEdit;
@@ -178,12 +178,36 @@
func: hir::Function,
impl_def: hir::Impl,
) {
- let fn_name = func.name(ctx.db);
+ let fn_name = &func.name(ctx.db);
+ let sugar: &[_] = if func.is_async(ctx.db) {
+ &[AsyncSugaring::Async, AsyncSugaring::Desugar]
+ } else if func.returns_impl_future(ctx.db) {
+ &[AsyncSugaring::Plain, AsyncSugaring::Resugar]
+ } else {
+ &[AsyncSugaring::Plain]
+ };
+ for &sugaring in sugar {
+ add_function_impl_(acc, ctx, replacement_range, func, impl_def, fn_name, sugaring);
+ }
+}
- let is_async = func.is_async(ctx.db);
+fn add_function_impl_(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ replacement_range: TextRange,
+ func: hir::Function,
+ impl_def: hir::Impl,
+ fn_name: &Name,
+ async_sugaring: AsyncSugaring,
+) {
+ let async_ = if let AsyncSugaring::Async | AsyncSugaring::Resugar = async_sugaring {
+ "async "
+ } else {
+ ""
+ };
let label = format_smolstr!(
"{}fn {}({})",
- if is_async { "async " } else { "" },
+ async_,
fn_name.display(ctx.db, ctx.edition),
if func.assoc_fn_params(ctx.db).is_empty() { "" } else { ".." }
);
@@ -195,22 +219,14 @@
});
let mut item = CompletionItem::new(completion_kind, replacement_range, label, ctx.edition);
- item.lookup_by(format!(
- "{}fn {}",
- if is_async { "async " } else { "" },
- fn_name.display(ctx.db, ctx.edition)
- ))
- .set_documentation(func.docs(ctx.db))
- .set_relevance(CompletionRelevance { is_item_from_trait: true, ..Default::default() });
+ item.lookup_by(format!("{}fn {}", async_, fn_name.display(ctx.db, ctx.edition)))
+ .set_documentation(func.docs(ctx.db))
+ .set_relevance(CompletionRelevance { is_item_from_trait: true, ..Default::default() });
if let Some(source) = ctx.sema.source(func) {
- let assoc_item = ast::AssocItem::Fn(source.value);
- if let Some(transformed_item) = get_transformed_assoc_item(ctx, assoc_item, impl_def) {
- let transformed_fn = match transformed_item {
- ast::AssocItem::Fn(func) => func,
- _ => unreachable!(),
- };
-
+ if let Some(transformed_fn) =
+ get_transformed_fn(ctx, source.value, impl_def, async_sugaring)
+ {
let function_decl = function_declaration(&transformed_fn, source.file_id.is_macro());
match ctx.config.snippet_cap {
Some(cap) => {
@@ -227,6 +243,14 @@
}
}
+#[derive(Copy, Clone)]
+enum AsyncSugaring {
+ Desugar,
+ Resugar,
+ Async,
+ Plain,
+}
+
/// Transform a relevant associated item to inline generics from the impl, remove attrs and docs, etc.
fn get_transformed_assoc_item(
ctx: &CompletionContext<'_>,
@@ -251,6 +275,82 @@
Some(assoc_item)
}
+/// Transform a relevant associated item to inline generics from the impl, remove attrs and docs, etc.
+fn get_transformed_fn(
+ ctx: &CompletionContext<'_>,
+ fn_: ast::Fn,
+ impl_def: hir::Impl,
+ async_: AsyncSugaring,
+) -> Option<ast::Fn> {
+ let trait_ = impl_def.trait_(ctx.db)?;
+ let source_scope = &ctx.sema.scope(fn_.syntax())?;
+ let target_scope = &ctx.sema.scope(ctx.sema.source(impl_def)?.syntax().value)?;
+ let transform = PathTransform::trait_impl(
+ target_scope,
+ source_scope,
+ trait_,
+ ctx.sema.source(impl_def)?.value,
+ );
+
+ let fn_ = fn_.clone_for_update();
+ // FIXME: Paths in nested macros are not handled well. See
+ // `macro_generated_assoc_item2` test.
+ transform.apply(fn_.syntax());
+ fn_.remove_attrs_and_docs();
+ match async_ {
+ AsyncSugaring::Desugar => {
+ match fn_.ret_type() {
+ Some(ret_ty) => {
+ let ty = ret_ty.ty()?;
+ ted::replace(
+ ty.syntax(),
+ make::ty(&format!("impl Future<Output = {ty}>"))
+ .syntax()
+ .clone_for_update(),
+ );
+ }
+ None => ted::append_child(
+ fn_.param_list()?.syntax(),
+ make::ret_type(make::ty("impl Future<Output = ()>"))
+ .syntax()
+ .clone_for_update(),
+ ),
+ }
+ fn_.async_token().unwrap().detach();
+ }
+ AsyncSugaring::Resugar => {
+ let ty = fn_.ret_type()?.ty()?;
+ match &ty {
+ // best effort guessing here
+ ast::Type::ImplTraitType(t) => {
+ let output = t.type_bound_list()?.bounds().find_map(|b| match b.ty()? {
+ ast::Type::PathType(p) => {
+ let p = p.path()?.segment()?;
+ if p.name_ref()?.text() != "Future" {
+ return None;
+ }
+ match p.generic_arg_list()?.generic_args().next()? {
+ ast::GenericArg::AssocTypeArg(a)
+ if a.name_ref()?.text() == "Output" =>
+ {
+ a.ty()
+ }
+ _ => None,
+ }
+ }
+ _ => None,
+ })?;
+ ted::replace(ty.syntax(), output.syntax());
+ }
+ _ => (),
+ }
+ ted::prepend_child(fn_.syntax(), make::token(T![async]));
+ }
+ AsyncSugaring::Async | AsyncSugaring::Plain => (),
+ }
+ Some(fn_)
+}
+
fn add_type_alias_impl(
acc: &mut Completions,
ctx: &CompletionContext<'_>,
@@ -1404,4 +1504,132 @@
"#,
);
}
+
+ #[test]
+ fn impl_fut() {
+ check_edit(
+ "fn foo",
+ r#"
+//- minicore: future, send, sized
+use core::future::Future;
+
+trait DesugaredAsyncTrait {
+ fn foo(&self) -> impl Future<Output = usize> + Send;
+}
+
+impl DesugaredAsyncTrait for () {
+ $0
+}
+"#,
+ r#"
+use core::future::Future;
+
+trait DesugaredAsyncTrait {
+ fn foo(&self) -> impl Future<Output = usize> + Send;
+}
+
+impl DesugaredAsyncTrait for () {
+ fn foo(&self) -> impl Future<Output = usize> + Send {
+ $0
+}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn impl_fut_resugared() {
+ check_edit(
+ "async fn foo",
+ r#"
+//- minicore: future, send, sized
+use core::future::Future;
+
+trait DesugaredAsyncTrait {
+ fn foo(&self) -> impl Future<Output = usize> + Send;
+}
+
+impl DesugaredAsyncTrait for () {
+ $0
+}
+"#,
+ r#"
+use core::future::Future;
+
+trait DesugaredAsyncTrait {
+ fn foo(&self) -> impl Future<Output = usize> + Send;
+}
+
+impl DesugaredAsyncTrait for () {
+ async fn foo(&self) -> usize {
+ $0
+}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn async_desugared() {
+ check_edit(
+ "fn foo",
+ r#"
+//- minicore: future, send, sized
+use core::future::Future;
+
+trait DesugaredAsyncTrait {
+ async fn foo(&self) -> usize;
+}
+
+impl DesugaredAsyncTrait for () {
+ $0
+}
+"#,
+ r#"
+use core::future::Future;
+
+trait DesugaredAsyncTrait {
+ async fn foo(&self) -> usize;
+}
+
+impl DesugaredAsyncTrait for () {
+ fn foo(&self) -> impl Future<Output = usize> {
+ $0
+}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn async_() {
+ check_edit(
+ "async fn foo",
+ r#"
+//- minicore: future, send, sized
+use core::future::Future;
+
+trait DesugaredAsyncTrait {
+ async fn foo(&self) -> usize;
+}
+
+impl DesugaredAsyncTrait for () {
+ $0
+}
+"#,
+ r#"
+use core::future::Future;
+
+trait DesugaredAsyncTrait {
+ async fn foo(&self) -> usize;
+}
+
+impl DesugaredAsyncTrait for () {
+ async fn foo(&self) -> usize {
+ $0
+}
+}
+"#,
+ );
+ }
}
diff --git a/crates/ide-completion/src/tests/item_list.rs b/crates/ide-completion/src/tests/item_list.rs
index 8aad7bf..532d492 100644
--- a/crates/ide-completion/src/tests/item_list.rs
+++ b/crates/ide-completion/src/tests/item_list.rs
@@ -313,6 +313,7 @@
ct const CONST1: () =
fn async fn function2()
fn fn function1()
+ fn fn function2()
ma makro!(…) macro_rules! makro
md module
ta type Type1 =
diff --git a/crates/project-model/src/workspace.rs b/crates/project-model/src/workspace.rs
index 7834238..4fc9ef3 100644
--- a/crates/project-model/src/workspace.rs
+++ b/crates/project-model/src/workspace.rs
@@ -1554,6 +1554,6 @@
fn add_dep_inner(graph: &mut CrateGraph, from: CrateId, dep: Dependency) {
if let Err(err) = graph.add_dep(from, dep) {
- tracing::error!("{}", err)
+ tracing::warn!("{}", err)
}
}
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
index abf1a1f..2eb9c1e 100644
--- a/crates/syntax/src/ast/make.rs
+++ b/crates/syntax/src/ast/make.rs
@@ -1162,7 +1162,7 @@
pub(super) static SOURCE_FILE: LazyLock<Parse<SourceFile>> = LazyLock::new(|| {
SourceFile::parse(
- "const C: <()>::Item = ( true && true , true || true , 1 != 1, 2 == 2, 3 < 3, 4 <= 4, 5 > 5, 6 >= 6, !true, *p, &p , &mut p, { let _ @ [] })\n;\n\nimpl A for B where: {}", Edition::CURRENT,
+ "const C: <()>::Item = ( true && true , true || true , 1 != 1, 2 == 2, 3 < 3, 4 <= 4, 5 > 5, 6 >= 6, !true, *p, &p , &mut p, async { let _ @ [] })\n;\n\nimpl A for B where: {}", Edition::CURRENT,
)
});
diff --git a/crates/syntax/src/ted.rs b/crates/syntax/src/ted.rs
index 29788d0..8592df1 100644
--- a/crates/syntax/src/ted.rs
+++ b/crates/syntax/src/ted.rs
@@ -147,6 +147,11 @@
insert_raw(position, child);
}
+pub fn prepend_child(node: &(impl Into<SyntaxNode> + Clone), child: impl Element) {
+ let position = Position::first_child_of(node);
+ insert(position, child);
+}
+
fn ws_before(position: &Position, new: &SyntaxElement) -> Option<SyntaxToken> {
let prev = match &position.repr {
PositionRepr::FirstChild(_) => return None,