diff --git a/lsp/tree-sitter-vine/grammar.js b/lsp/tree-sitter-vine/grammar.js index be375b4f..9ebc7a53 100644 --- a/lsp/tree-sitter-vine/grammar.js +++ b/lsp/tree-sitter-vine/grammar.js @@ -584,6 +584,7 @@ module.exports = grammar({ $.chain_deref, $.chain_inverse, $.chain_as, + $.chain_index, ), chain_unwrap: $ => "!", @@ -595,6 +596,7 @@ module.exports = grammar({ chain_deref: $ => seq(".", "*"), chain_inverse: $ => seq(".", "~"), chain_as: $ => seq(".", "as", "[", $._ty, "]"), + chain_index: $ => seq(".", "[", $._expr, "]"), string: $ => seq( diff --git a/root/data/Array.vi b/root/data/Array.vi index 472fc32d..ab31667e 100644 --- a/root/data/Array.vi +++ b/root/data/Array.vi @@ -1,5 +1,5 @@ -use ops::{Cast, comparison::Eq}; +use ops::{Cast, Index::{Index, IndexPlace, IndexSpace, IndexValue}, comparison::Eq}; /// A contiguous, indexed collection of `T`. /// @@ -432,6 +432,18 @@ pub mod Array { Show::Constructor("Array", self.&.as[&List].*.show()) } } + + pub impl [T]: Index[Array[T], N32, T] {} + + pub impl [T]: IndexPlace[Array[T], N32, T] { + fn assume_at(&self: &Array[T], index: N32) -> &T { + self.at(index).assume() + } + } + + pub impl [T?]: IndexSpace[Array[T], N32, T]; + + pub impl [T+]: IndexValue[Array[T], N32, T]; } /// The internal tree structure of an array. diff --git a/root/data/Map.vi b/root/data/Map.vi index adae9e97..9897c219 100644 --- a/root/data/Map.vi +++ b/root/data/Map.vi @@ -1,6 +1,6 @@ use data::Iterator::{Iterator, Collect}; -use ops::{Cast, Concat, comparison::Ord}; +use ops::{Cast, Concat, Index::{Index, IndexPlace, IndexSpace, IndexValue}, comparison::Ord}; pub struct* Map[K, V](Set[(K, V)]); @@ -108,6 +108,32 @@ pub mod Map[K, V; Ord[K]] { )) } + pub impl index[K, V]: Index[Map[K, V], K, V] {} + + pub impl index_value[K?, V+; Ord[K]]: IndexValue[Map[K, V], K, V]; + + pub impl index_place[K?, V; Ord[K]]: IndexPlace[Map[K, V], K, V] { + fn assume_at(&self: &Map[K, V], key: K) -> &V { + self.at(&key).assume() + } + } + + pub impl index_space[K?, V?; Ord[K]]: IndexSpace[Map[K, V], K, V] { + fn assume_set(&self: &Map[K, V], key: K, value: V) { + self.insert(key, value); + } + } + + pub impl index_ref[K, V]: Index[Map[K, V], &K, V] {} + + pub impl index_value_ref[K+, V+; Ord[K]]: IndexValue[Map[K, V], &K, V]; + + pub impl index_place_ref[K, V; Ord[K]]: IndexPlace[Map[K, V], &K, V] { + fn assume_at(&self: &Map[K, V], &key: &K) -> &V { + self.at(&key).assume() + } + } + pub impl iter_ref[...]: Cast[&Map[K, V], IterRef[K, V]] { fn cast(&self: &Map[K, V]) -> IterRef[K, V] { IterRef(self!.iter_ref()) diff --git a/root/ops/Index.vi b/root/ops/Index.vi new file mode 100644 index 00000000..547208c9 --- /dev/null +++ b/root/ops/Index.vi @@ -0,0 +1,36 @@ + +#[builtin = "Index"] +pub trait Index[T, K, V] {} + +#[builtin = "IndexValue"] +pub trait IndexValue[T, K, V] { + fn .assume_get(self: &T, key: K) -> V; +} + +pub mod IndexValue { + #[basic] + pub impl by_place[T, K, V+; IndexPlace[T, K, V]]: IndexValue[T, K, V] { + fn assume_get(&self: &T, key: K) -> V { + *self.assume_at(key) + } + } +} + +#[builtin = "IndexSpace"] +pub trait IndexSpace[T, K, V] { + fn .assume_set(self: &T, key: K, value: V); +} + +pub mod IndexSpace { + #[basic] + pub impl by_place[T, K, V?; IndexPlace[T, K, V]]: IndexSpace[T, K, V] { + fn assume_set(&self: &T, key: K, value: V) { + *self.assume_at(key) = value; + } + } +} + +#[builtin = "IndexPlace"] +pub trait IndexPlace[T, K, V] { + fn .assume_at(self: &T, key: K) -> &V; +} diff --git a/root/ops/ops.vi b/root/ops/ops.vi index c28b39ac..07b1ff8b 100644 --- a/root/ops/ops.vi +++ b/root/ops/ops.vi @@ -4,6 +4,7 @@ pub mod bitwise; pub mod comparison; pub mod elementwise; pub mod flex; +pub mod Index; pub mod Range; pub trait Concat[A, B, O] { diff --git a/tests/programs/repl/index.vi b/tests/programs/repl/index.vi new file mode 100644 index 00000000..cdcb6b8a --- /dev/null +++ b/tests/programs/repl/index.vi @@ -0,0 +1,18 @@ + +use #root::data::{Array, Map}; + +let words = ["i", "have", "a", "dream"] as Array +words.[0] +words.[1] +words.[2] +words.[3] +words.[0] = "I" +words.[3] ++= "." +/clear words + +let foods = Map::empty[String, String] +foods.["chicken"] = "tasty" +foods.["broccoli"] = "healthy" +foods.[&"broccoli"] ++= " and colorful" +foods.["chicken"] +foods.[&"chicken"] diff --git a/tests/snaps/vine/repl/index.repl.vi b/tests/snaps/vine/repl/index.repl.vi new file mode 100644 index 00000000..0f05c1ae --- /dev/null +++ b/tests/snaps/vine/repl/index.repl.vi @@ -0,0 +1,75 @@ + +let io: IO = ; +> + +let io: IO = ; +> use #root::data::{Array, Map}; + +let io: IO = ; +> + +let io: IO = ; +> let words = ["i", "have", "a", "dream"] as Array + +let io: IO = ; +let words: Array[String] = Array(["i", "have", "a", "dream"]); +> words.[0] +"i" + +let io: IO = ; +let words: Array[String] = Array(["i", "have", "a", "dream"]); +> words.[1] +"have" + +let io: IO = ; +let words: Array[String] = Array(["i", "have", "a", "dream"]); +> words.[2] +"a" + +let io: IO = ; +let words: Array[String] = Array(["i", "have", "a", "dream"]); +> words.[3] +"dream" + +let io: IO = ; +let words: Array[String] = Array(["i", "have", "a", "dream"]); +> words.[0] = "I" + +let io: IO = ; +let words: Array[String] = Array(["I", "have", "a", "dream"]); +> words.[3] ++= "." + +let io: IO = ; +let words: Array[String] = Array(["I", "have", "a", "dream."]); +> /clear words + +let io: IO = ; +> + +let io: IO = ; +> let foods = Map::empty[String, String] + +let io: IO = ; +let foods: Map[String, String] = Map({}); +> foods.["chicken"] = "tasty" + +let io: IO = ; +let foods: Map[String, String] = Map({ "chicken": "tasty" }); +> foods.["broccoli"] = "healthy" + +let io: IO = ; +let foods: Map[String, String] = Map({ "broccoli": "healthy", "chicken": "tasty" }); +> foods.[&"broccoli"] ++= " and colorful" + +let io: IO = ; +let foods: Map[String, String] = Map({ "broccoli": "healthy and colorful", "chicken": "tasty" }); +> foods.["chicken"] +"tasty" + +let io: IO = ; +let foods: Map[String, String] = Map({ "broccoli": "healthy and colorful", "chicken": "tasty" }); +> foods.[&"chicken"] +"tasty" + +let io: IO = ; +let foods: Map[String, String] = Map({ "broccoli": "healthy and colorful", "chicken": "tasty" }); diff --git a/tests/tests.rs b/tests/tests.rs index c9a58fcc..3eeedc41 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -163,6 +163,7 @@ fn tests(t: &mut DynTester) { test_vi_repl(t, "tests/programs/repl/F64.vi"); test_vi_repl(t, "tests/programs/repl/heap.vi"); test_vi_repl(t, "tests/programs/repl/i32_misc.vi"); + test_vi_repl(t, "tests/programs/repl/index.vi"); test_vi_repl(t, "tests/programs/repl/misc.vi"); test_vi_repl(t, "tests/programs/repl/N64.vi"); test_vi_repl(t, "tests/programs/repl/Nat.vi"); diff --git a/vine/src/components/distiller.rs b/vine/src/components/distiller.rs index c3dc39a9..c89e07d8 100644 --- a/vine/src/components/distiller.rs +++ b/vine/src/components/distiller.rs @@ -6,15 +6,17 @@ use vine_util::{ }; use crate::{ - components::finder::FinderCache, + components::finder::{Finder, FinderCache}, structures::{ ast::Span, chart::{Chart, DefId, GenericsId}, diag::{Diag, Diags, ErrorGuaranteed}, resolutions::{Fragment, Rels}, signatures::Signatures, - tir::{ClosureId, Local, TargetId, TirExpr, TirExprKind, TirLocal, TirPat, TirPatKind}, - types::{Type, Types}, + tir::{ + ClosureId, Local, TargetId, TirExpr, TirExprKind, TirImpl, TirLocal, TirPat, TirPatKind, + }, + types::{ImplType, Type, Types}, vir::{ Header, Interface, InterfaceId, InterfaceKind, Layer, LayerId, Port, Stage, StageId, Step, Transfer, Vir, VirLocal, @@ -196,6 +198,19 @@ impl<'r> Distiller<'r> { local } + pub(crate) fn find_impl(&mut self, span: Span, ty: &ImplType, basic: bool) -> TirImpl { + let mut finder = Finder::new( + self.chart, + self.sigs, + self.diags, + self.finder_cache, + self.def, + self.generics, + span, + ); + finder.find_impl(&mut self.types, ty, basic) + } + pub(crate) fn distill_vec( &mut self, stage: &mut Stage, @@ -297,6 +312,9 @@ impl<'r> Distiller<'r> { TirExprKind::Call(rel, receiver, args) => { self.distill_expr_value_call(stage, span, ty, *rel, receiver, args) } + TirExprKind::Index(expr, index) => { + self.distill_expr_value_index(stage, span, expr, index, ty) + } TirExprKind::String(init, rest) => { self.distill_expr_value_string(stage, span, ty, init, rest) } @@ -328,6 +346,9 @@ impl<'r> Distiller<'r> { TirExprKind::Composite(elements) => { self.distill_expr_space_composite(stage, span, ty, elements) } + TirExprKind::Index(expr, index) => { + self.distill_expr_space_index(stage, span, expr, index, ty) + } } } @@ -350,6 +371,9 @@ impl<'r> Distiller<'r> { TirExprKind::Composite(elements) => { self.distill_expr_place_composite(stage, span, ty, elements) } + TirExprKind::Index(expr, index) => { + self.distill_expr_place_index(stage, span, expr, index, ty) + } } } @@ -373,6 +397,7 @@ impl<'r> Distiller<'r> { TirExprKind::Field(inner, index, fields) => { self.distill_expr_poly_field(stage, span, ty, inner, *index, fields) } + TirExprKind::Index(expr, index) => self.distill_expr_poly_index(stage, span, expr, index, ty), } } diff --git a/vine/src/components/parser.rs b/vine/src/components/parser.rs index 7fa135a5..1c636767 100644 --- a/vine/src/components/parser.rs +++ b/vine/src/components/parser.rs @@ -370,6 +370,11 @@ impl<'src> Parser<'src> { self.expect(Token::CloseBracket)?; return Ok(Ok(ExprKind::Cast(lhs, ty, true))); } + if self.eat(Token::OpenBracket)? { + let index = self.parse_expr()?; + self.expect(Token::CloseBracket)?; + return Ok(Ok(ExprKind::Index(lhs, index))); + } let ident_span = self.span(); let ident = self.parse_ident()?; if self.check(Token::OpenBracket) || self.check(Token::OpenParen) { diff --git a/vine/src/components/resolver.rs b/vine/src/components/resolver.rs index 7248d915..ca8c6458 100644 --- a/vine/src/components/resolver.rs +++ b/vine/src/components/resolver.rs @@ -517,6 +517,7 @@ impl<'a> Resolver<'a> { } ExprKind::ComparisonOp(init, cmps) => self.resolve_expr_comparison_op(span, init, cmps), ExprKind::Cast(inner, to, _) => self.resolve_expr_cast(span, inner, to), + ExprKind::Index(expr, index) => self.resolve_expr_index(span, expr, index), ExprKind::RangeExclusive(start, end) => { self.resolve_expr_range(span, start.as_ref(), end.as_ref(), false) } diff --git a/vine/src/features.rs b/vine/src/features.rs index c30a07be..efd32fda 100644 --- a/vine/src/features.rs +++ b/vine/src/features.rs @@ -18,6 +18,7 @@ pub mod hole; pub mod if_; pub mod impl_; pub mod import; +pub mod index; pub mod inline_ivy; pub mod inverse; pub mod labels; diff --git a/vine/src/features/builtin.rs b/vine/src/features/builtin.rs index 8dc77c19..5a70ded7 100644 --- a/vine/src/features/builtin.rs +++ b/vine/src/features/builtin.rs @@ -39,6 +39,10 @@ pub enum Builtin { Drop, Duplicate, Erase, + Index, + IndexValue, + IndexSpace, + IndexPlace, Range, BoundUnbounded, BoundInclusive, @@ -104,6 +108,10 @@ impl Parser<'_> { "Drop" => Builtin::Drop, "duplicate" => Builtin::Duplicate, "erase" => Builtin::Erase, + "Index" => Builtin::Index, + "IndexValue" => Builtin::IndexValue, + "IndexSpace" => Builtin::IndexSpace, + "IndexPlace" => Builtin::IndexPlace, "Range" => Builtin::Range, "BoundUnbounded" => Builtin::BoundUnbounded, "BoundInclusive" => Builtin::BoundInclusive, @@ -157,6 +165,11 @@ pub struct Builtins { pub duplicate: Option, pub erase: Option, + pub index: Option, + pub index_value: Option, + pub index_space: Option, + pub index_place: Option, + pub range: Option, pub bound_exclusive: Option, pub bound_inclusive: Option, @@ -241,6 +254,10 @@ impl Charter<'_> { Builtin::BoolNot => set(&mut builtins.bool_not, impl_id), Builtin::BinaryOp(op) => set(builtins.binary_ops.entry(op).or_default(), fn_id), Builtin::ComparisonOp(op) => set(builtins.comparison_ops.entry(op).or_default(), fn_id), + Builtin::Index => set(&mut builtins.index, trait_id), + Builtin::IndexValue => set(&mut builtins.index_value, trait_id), + Builtin::IndexSpace => set(&mut builtins.index_space, trait_id), + Builtin::IndexPlace => set(&mut builtins.index_place, trait_id), Builtin::Range => set(&mut builtins.range, struct_id), Builtin::BoundUnbounded => set(&mut builtins.bound_unbounded, struct_id), Builtin::BoundExclusive => set(&mut builtins.bound_exclusive, struct_id), diff --git a/vine/src/features/coerce.rs b/vine/src/features/coerce.rs index e46e3030..6e690838 100644 --- a/vine/src/features/coerce.rs +++ b/vine/src/features/coerce.rs @@ -132,7 +132,7 @@ impl Distiller<'_> { value } None => { - let diag = Diag::CannotFork { span, ty: self.types.show(self.chart, ty.inverse()) }; + let diag = Diag::CannotDrop { span, ty: self.types.show(self.chart, ty.inverse()) }; Port::error(ty, self.diags.error(diag)) } }, @@ -144,16 +144,7 @@ impl Distiller<'_> { self.diags.error(Diag::MissingBuiltin { span, builtin: "Drop" }); return; }; - let mut finder = Finder::new( - self.chart, - self.sigs, - self.diags, - self.finder_cache, - self.def, - self.generics, - span, - ); - let impl_ = finder.find_impl(&mut self.types, &ImplType::Trait(drop, vec![ty]), false); + let impl_ = self.find_impl(span, &ImplType::Trait(drop, vec![ty]), false); let fn_rel = self.rels.drop_rel(self.chart, impl_); let nil = Port { ty: self.types.nil(), kind: PortKind::Nil }; stage.steps.push(Step::Call(span, fn_rel, None, vec![port], nil)); diff --git a/vine/src/features/index.rs b/vine/src/features/index.rs new file mode 100644 index 00000000..44943513 --- /dev/null +++ b/vine/src/features/index.rs @@ -0,0 +1,145 @@ +use crate::{ + components::{ + distiller::{Distiller, Poly}, + resolver::Resolver, + }, + structures::{ + ast::{Expr, Span}, + chart::{FnId, TraitFnId}, + diag::Diag, + resolutions::FnRel, + tir::{TirExpr, TirExprKind}, + types::{ImplType, Type}, + vir::{Port, PortKind, Stage, Step}, + }, + tools::fmt::{Formatter, doc::Doc}, +}; + +impl<'src> Formatter<'src> { + pub(crate) fn fmt_expr_index(&self, expr: &Expr, index: &Expr) -> Doc<'src> { + Doc::concat([self.fmt_expr(expr), Doc(".["), self.fmt_expr(index), Doc("]")]) + } +} + +impl Resolver<'_> { + pub(crate) fn resolve_expr_index( + &mut self, + span: Span, + expr: &Expr, + index: &Expr, + ) -> Result { + let Some(index_trait) = self.chart.builtins.index else { + Err(Diag::MissingBuiltin { span, builtin: "Index" })? + }; + + let expr = self.resolve_expr(expr); + let index = self.resolve_expr(index); + let value_ty = self.types.new_var(span); + let impl_type = ImplType::Trait(index_trait, vec![expr.ty, index.ty, value_ty]); + let _ = self.find_impl(span, &impl_type, false); + + Ok(TirExpr::new(span, value_ty, TirExprKind::Index(expr, index))) + } +} + +impl Distiller<'_> { + pub(crate) fn distill_expr_value_index( + &mut self, + stage: &mut Stage, + span: Span, + expr: &TirExpr, + index: &TirExpr, + value_ty: Type, + ) -> Port { + let Some(index_value_trait) = self.chart.builtins.index_value else { + return Port::error( + value_ty, + self.diags.error(Diag::MissingBuiltin { span, builtin: "IndexValue" }), + ); + }; + + let (expr_pos, expr_neg) = self.distill_expr_place(stage, expr); + let expr_ref = stage.ref_place(span, expr.ty, (expr_pos, expr_neg)); + let impl_type = ImplType::Trait(index_value_trait, vec![expr.ty, index.ty, value_ty]); + let index_value_impl = self.find_impl(span, &impl_type, false); + let assume_get_rel = + FnRel::Item(FnId::Abstract(index_value_trait, TraitFnId(0)), vec![index_value_impl]); + let assume_get_rel_id = self.rels.fns.push(assume_get_rel); + let args = vec![expr_ref, self.distill_expr_value(stage, index)]; + let value = stage.new_wire(span, value_ty); + stage.steps.push(Step::Call(span, assume_get_rel_id, None, args, value.neg)); + value.pos + } + + pub(crate) fn distill_expr_space_index( + &mut self, + stage: &mut Stage, + span: Span, + expr: &TirExpr, + index: &TirExpr, + value_ty: Type, + ) -> Port { + let Some(index_space_trait) = self.chart.builtins.index_space else { + return Port::error( + value_ty, + self.diags.error(Diag::MissingBuiltin { span, builtin: "IndexSpace" }), + ); + }; + + let (expr_pos, expr_neg) = self.distill_expr_place(stage, expr); + let expr_ref = stage.ref_place(span, expr.ty, (expr_pos, expr_neg)); + let impl_type = ImplType::Trait(index_space_trait, vec![expr.ty, index.ty, value_ty]); + let index_space_impl = self.find_impl(span, &impl_type, false); + let assume_set_rel = + FnRel::Item(FnId::Abstract(index_space_trait, TraitFnId(0)), vec![index_space_impl]); + let assume_set_rel_id = self.rels.fns.push(assume_set_rel); + let space = stage.new_wire(span, value_ty); + let args = vec![expr_ref, self.distill_expr_value(stage, index), space.pos]; + let nil = Port { ty: self.types.nil(), kind: PortKind::Nil }; + stage.steps.push(Step::Call(span, assume_set_rel_id, None, args, nil)); + space.neg + } + + pub(crate) fn distill_expr_place_index( + &mut self, + stage: &mut Stage, + span: Span, + expr: &TirExpr, + index: &TirExpr, + value_ty: Type, + ) -> (Port, Port) { + let Some(index_place_trait) = self.chart.builtins.index_place else { + let err = Port::error( + value_ty, + self.diags.error(Diag::MissingBuiltin { span, builtin: "IndexPlace" }), + ); + return (err.clone(), err); + }; + + let (expr_pos, expr_neg) = self.distill_expr_place(stage, expr); + let expr_ref = stage.ref_place(span, expr.ty, (expr_pos, expr_neg)); + let impl_type = ImplType::Trait(index_place_trait, vec![expr.ty, index.ty, value_ty]); + let index_place_impl = self.find_impl(span, &impl_type, false); + let assume_at_rel = + FnRel::Item(FnId::Abstract(index_place_trait, TraitFnId(0)), vec![index_place_impl]); + let assume_at_rel_id = self.rels.fns.push(assume_at_rel); + let args = vec![expr_ref, self.distill_expr_value(stage, index)]; + let wire = stage.new_wire(span, value_ty); + stage.steps.push(Step::Call(span, assume_at_rel_id, None, args, wire.neg)); + let value = stage.new_wire(span, value_ty); + let space = stage.new_wire(span, value_ty); + stage.steps.push(Step::Ref(wire.pos, value.neg, space.pos)); + (value.pos, space.neg) + } + + pub(crate) fn distill_expr_poly_index( + &mut self, + stage: &mut Stage, + span: Span, + expr: &TirExpr, + index: &TirExpr, + value_ty: Type, + ) -> Poly { + Poly::Place(self.distill_expr_place_index(stage, span, expr, index, value_ty)) + } +} diff --git a/vine/src/structures/ast.rs b/vine/src/structures/ast.rs index b9ffcce3..f4c0a465 100644 --- a/vine/src/structures/ast.rs +++ b/vine/src/structures/ast.rs @@ -334,6 +334,7 @@ pub enum ExprKind { Cast(Expr, Ty, bool), Unwrap(Expr), Try(Expr), + Index(Expr, Expr), RangeExclusive(Option, Option), RangeInclusive(Option, Expr), N32(u32), diff --git a/vine/src/structures/ast/visit.rs b/vine/src/structures/ast/visit.rs index ecda6b29..a028c26f 100644 --- a/vine/src/structures/ast/visit.rs +++ b/vine/src/structures/ast/visit.rs @@ -190,6 +190,10 @@ pub trait VisitMut<'a> { self.visit_expr(e); self.visit_type(t); } + ExprKind::Index(expr, index) => { + self.visit_expr(expr); + self.visit_expr(index); + } ExprKind::RangeExclusive(start, end) => { self.visit(start.as_mut()); self.visit(end.as_mut()); diff --git a/vine/src/structures/checkpoint.rs b/vine/src/structures/checkpoint.rs index 648b0780..a73137af 100644 --- a/vine/src/structures/checkpoint.rs +++ b/vine/src/structures/checkpoint.rs @@ -240,6 +240,10 @@ impl Builtins { drop, duplicate, erase, + index, + index_value, + index_space, + index_place, range, bound_exclusive, bound_inclusive, @@ -282,6 +286,10 @@ impl Builtins { revert_idx(drop, checkpoint.traits); revert_idx(duplicate, checkpoint.impls); revert_idx(erase, checkpoint.impls); + revert_idx(index, checkpoint.traits); + revert_idx(index_value, checkpoint.traits); + revert_idx(index_space, checkpoint.traits); + revert_idx(index_place, checkpoint.traits); revert_idx(range, checkpoint.structs); revert_idx(bound_exclusive, checkpoint.structs); revert_idx(bound_inclusive, checkpoint.structs); diff --git a/vine/src/structures/tir.rs b/vine/src/structures/tir.rs index f63a3c30..7d07acfb 100644 --- a/vine/src/structures/tir.rs +++ b/vine/src/structures/tir.rs @@ -99,6 +99,8 @@ pub enum TirExprKind { Field(TirExpr, usize, Vec), #[class(value)] Call(FnRelId, Option, Vec), + #[class(poly, value, space, place)] + Index(TirExpr, TirExpr), #[class(poly, value, place, space)] Struct(StructId, TirExpr), #[class(value)] diff --git a/vine/src/tools/fmt.rs b/vine/src/tools/fmt.rs index 46b777a9..02f7803a 100644 --- a/vine/src/tools/fmt.rs +++ b/vine/src/tools/fmt.rs @@ -213,6 +213,7 @@ impl<'src> Formatter<'src> { ExprKind::Cast(expr, ty, postfix) => self.fmt_expr_cast(expr, ty, *postfix), ExprKind::Unwrap(expr) => self.fmt_expr_unwrap(expr), ExprKind::Try(expr) => self.fmt_expr_try(expr), + ExprKind::Index(expr, index) => self.fmt_expr_index(expr, index), ExprKind::RangeExclusive(start, end) => self.fmt_expr_range_exclusive(start, end), ExprKind::RangeInclusive(start, end) => self.fmt_expr_range_inclusive(start, end), ExprKind::Place(value, space) => self.fmt_expr_place(value, space),