From 1dac3429f6516a6da0d09b6fc7d4d2e59046d1af Mon Sep 17 00:00:00 2001 From: Kisaragi Marine Date: Thu, 19 Oct 2023 23:47:34 +0900 Subject: [PATCH 1/2] refactor: cargo clippy --fix --- .../src/lib.rs | 2 +- package/origlang-typesystem-model/src/lib.rs | 48 +++++++++---------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/package/origlang-compiler-scanner-example/src/lib.rs b/package/origlang-compiler-scanner-example/src/lib.rs index 37bcf735..2bae020e 100644 --- a/package/origlang-compiler-scanner-example/src/lib.rs +++ b/package/origlang-compiler-scanner-example/src/lib.rs @@ -79,6 +79,6 @@ mod tests { .register_diagnostic_receiver(Box::new(tdr)) .compile("var foo = 1i32\n".to_string()); - assert!(a.is_ok()) + assert!(a.is_ok()); } } diff --git a/package/origlang-typesystem-model/src/lib.rs b/package/origlang-typesystem-model/src/lib.rs index 4aa6eb02..6235da6d 100644 --- a/package/origlang-typesystem-model/src/lib.rs +++ b/package/origlang-typesystem-model/src/lib.rs @@ -33,7 +33,7 @@ pub struct DisplayRecordType { } impl DisplayRecordType { - pub fn new(identifier: Identifier, components: Vec) -> Self { + #[must_use] pub fn new(identifier: Identifier, components: Vec) -> Self { Self { identifier, components } @@ -78,15 +78,15 @@ pub enum Type { } impl Type { - pub const fn is_int_family(&self) -> bool { + #[must_use] pub const fn is_int_family(&self) -> bool { matches!(self, Self::GenericInteger | Self::Int8 | Self::Int16 | Self::Int32 | Self::Int64) } - pub fn tuple(tuple_elements: Vec) -> Self { + #[must_use] pub fn tuple(tuple_elements: Vec) -> Self { Self::Tuple(DisplayTupleType(tuple_elements)) } - pub fn is_assignable(&self, from: &Self) -> AssignableQueryAnswer { + #[must_use] pub fn is_assignable(&self, from: &Self) -> AssignableQueryAnswer { if self.is_int_family() && from == &Self::GenericInteger { AssignableQueryAnswer::PossibleIfCoerceSourceImplicitly } else if self == from { @@ -96,7 +96,7 @@ impl Type { } } - pub fn as_tuple(&self) -> Option<&DisplayTupleType> { + #[must_use] pub fn as_tuple(&self) -> Option<&DisplayTupleType> { if let Self::Tuple(x) = self { Some(x) } else { @@ -176,25 +176,25 @@ pub enum TypedExpression { } impl TypedExpression { - pub fn actual_type(&self) -> Type { + #[must_use] pub fn actual_type(&self) -> Type { match self { - TypedExpression::IntLiteral(i) => i.actual_type(), - TypedExpression::BooleanLiteral(_) => Type::Boolean, - TypedExpression::StringLiteral(_) => Type::String, - TypedExpression::UnitLiteral => Type::Unit, - TypedExpression::Variable { tp, .. } => tp.clone(), - TypedExpression::BinaryOperator { return_type, .. } => return_type.clone(), - TypedExpression::If { return_type, .. } => return_type.clone(), - TypedExpression::Block { return_type, .. } => return_type.clone(), - TypedExpression::Tuple { expressions } => Type::tuple(expressions.iter().map(|x| x.actual_type()).collect()), - TypedExpression::ExtractTuple { expr, index } => { + Self::IntLiteral(i) => i.actual_type(), + Self::BooleanLiteral(_) => Type::Boolean, + Self::StringLiteral(_) => Type::String, + Self::UnitLiteral => Type::Unit, + Self::Variable { tp, .. } => tp.clone(), + Self::BinaryOperator { return_type, .. } => return_type.clone(), + Self::If { return_type, .. } => return_type.clone(), + Self::Block { return_type, .. } => return_type.clone(), + Self::Tuple { expressions } => Type::tuple(expressions.iter().map(Self::actual_type).collect()), + Self::ExtractTuple { expr, index } => { expr.actual_type().as_tuple().map(|y| y.0[*index].clone()).expect("the underlying expression must be tuple and index must be within its bound") } } } - pub fn tuple_arity(&self) -> Option { - if let TypedExpression::Tuple { expressions } = self { + #[must_use] pub fn tuple_arity(&self) -> Option { + if let Self::Tuple { expressions } = self { Some(expressions.len()) } else { None @@ -212,13 +212,13 @@ pub enum TypedIntLiteral { } impl TypedIntLiteral { - pub const fn actual_type(&self) -> Type { + #[must_use] pub const fn actual_type(&self) -> Type { match self { - TypedIntLiteral::Generic(_) => Type::GenericInteger, - TypedIntLiteral::Bit64(_) => Type::Int64, - TypedIntLiteral::Bit32(_) => Type::Int32, - TypedIntLiteral::Bit16(_) => Type::Int16, - TypedIntLiteral::Bit8(_) => Type::Int8, + Self::Generic(_) => Type::GenericInteger, + Self::Bit64(_) => Type::Int64, + Self::Bit32(_) => Type::Int32, + Self::Bit16(_) => Type::Int16, + Self::Bit8(_) => Type::Int8, } } } From 475e0f8878b0d40a2217b13d8341ae47de6133e9 Mon Sep 17 00:00:00 2001 From: Kisaragi Marine Date: Fri, 20 Oct 2023 00:09:07 +0900 Subject: [PATCH 2/2] refactor: reduce warnings --- package/origlang-cli/src/task/emit.rs | 9 ++- package/origlang-cli/src/task/repl.rs | 22 -------- .../origlang-compiler-entrypoint/src/lib.rs | 2 + package/origlang-compiler/src/lexer.rs | 55 +++++++++---------- package/origlang-compiler/src/lexer/error.rs | 1 + package/origlang-compiler/src/lexer/token.rs | 4 +- package/origlang-compiler/src/parser.rs | 4 +- package/origlang-compiler/src/type_check.rs | 14 ++--- package/origlang-ir-optimizer/src/ir0.rs | 1 + package/origlang-ir-optimizer/src/ir1.rs | 2 + package/origlang-ir-optimizer/src/preset.rs | 7 +++ package/origlang-ir/src/lib.rs | 11 +--- package/origlang-runtime/src/invoke_once.rs | 2 +- package/origlang-runtime/src/lib.rs | 11 ++-- package/origlang-typesystem-model/src/lib.rs | 6 +- 15 files changed, 65 insertions(+), 86 deletions(-) diff --git a/package/origlang-cli/src/task/emit.rs b/package/origlang-cli/src/task/emit.rs index ac0d3e2c..be147bb2 100644 --- a/package/origlang-cli/src/task/emit.rs +++ b/package/origlang-cli/src/task/emit.rs @@ -3,7 +3,7 @@ use origlang_compiler::lexer::Lexer; use origlang_compiler::parser::{Parser, SimpleErrorWithPos}; use origlang_compiler::type_check::error::TypeCheckError; use origlang_compiler::type_check::TypeChecker; -use origlang_ir::{IR1, IR2}; +use origlang_ir::{IR1, IR2, IntoVerbatimSequencedIR}; use origlang_ir_optimizer::lower::{EachStep, LowerStep, TheTranspiler}; use origlang_ir_optimizer::preset::{NoOptimization, SimpleOptimization}; use crate::args::{EmitPhase, OptimizeLevel, ParseSource}; @@ -57,15 +57,14 @@ impl Task for UnstableEmit { } let checker = TypeChecker::new(); - let checked = checker.check(root)?; + let expr = checker.check(root)?; if self.phase == EmitPhase::TypedAst { - println!("{checked:#?}"); + println!("{expr:#?}"); return Ok(()) } - use origlang_ir::IntoVerbatimSequencedIR; - let ir_sequence = checked.into_ir(); + let ir_sequence = expr.into_ir(); let optimizer = match optimize_level { OptimizeLevel::None => &NoOptimization as &dyn EachStep, diff --git a/package/origlang-cli/src/task/repl.rs b/package/origlang-cli/src/task/repl.rs index 11810096..cee2fc19 100644 --- a/package/origlang-cli/src/task/repl.rs +++ b/package/origlang-cli/src/task/repl.rs @@ -28,28 +28,6 @@ impl ariadne::Cache<()> for Dummy { pub struct Repl; impl Repl { - fn type_box_to_final_evaluated_form(tb: &TypeBox) -> String { - match tb { - TypeBox::Int8(i) => i.to_string(), - TypeBox::Int16(i) => i.to_string(), - TypeBox::Int32(i) => i.to_string(), - TypeBox::Int64(i) | TypeBox::NonCoercedInteger(i) => i.to_string(), - TypeBox::Boolean(b) => b.to_string(), - TypeBox::String(s) => format!(r#""{s}""#), - TypeBox::Unit => "()".to_string(), - TypeBox::Tuple(tp) => { - let elements = tp.boxes.iter().map(Self::type_box_to_final_evaluated_form).collect::>().join(", "); - format!("({elements})") - } - TypeBox::Record(r) => { - let elements = r.values.iter().map(Self::type_box_to_final_evaluated_form).collect::>().join(", "); - let identifier = r.name.clone(); - - format!("{identifier} {{{elements}}}") - } - } - } - fn naive_lower(tra: TypedRootAst) -> Vec { let ir = tra.into_ir(); let trans = TheTranspiler::new(&NoOptimization); diff --git a/package/origlang-compiler-entrypoint/src/lib.rs b/package/origlang-compiler-entrypoint/src/lib.rs index 0dcf535c..d51270a5 100644 --- a/package/origlang-compiler-entrypoint/src/lib.rs +++ b/package/origlang-compiler-entrypoint/src/lib.rs @@ -32,6 +32,7 @@ impl TheCompiler { } } + #[must_use] pub fn scanners(mut self, modify: impl FnOnce(&mut ScannerRegistry)) -> Self { modify(&mut self.scanner); @@ -46,6 +47,7 @@ impl TheCompiler { self } + #[must_use] pub fn optimization_preset(mut self, modify: impl FnOnce(&mut OptimizationPresetCollection)) -> Self { modify(&mut self.optimization_preset); diff --git a/package/origlang-compiler/src/lexer.rs b/package/origlang-compiler/src/lexer.rs index 5f16d1df..1b2ba9a7 100644 --- a/package/origlang-compiler/src/lexer.rs +++ b/package/origlang-compiler/src/lexer.rs @@ -42,12 +42,18 @@ pub struct Lexer<'src> { impl<'src> Lexer<'src> { #[must_use = "Lexer do nothing unless calling parsing function"] + // NOTE: unsafe { NonZeroUsize::new_unchecked(1) } is same as NonZeroUsize::new(1).expect() in + // release mode. + // However, clippy::missing_panics_doc issues that the latter may panic (obviously, it's + // false positive). pub fn create(source: &'src str) -> Self { Self { source_bytes_nth: Cell::new(Utf8CharBoundaryStartByte::new(0)), source, - line: Cell::new(NonZeroUsize::new(1).unwrap()), - column: Cell::new(NonZeroUsize::new(1).unwrap()), + // SAFETY: 1 != 0 + line: Cell::new(unsafe { NonZeroUsize::new_unchecked(1) }), + // SAFETY: 1 != 0 + column: Cell::new(unsafe { NonZeroUsize::new_unchecked(1) }), } } } @@ -70,18 +76,14 @@ impl Lexer<'_> { trace!("lexer:try:{s:?}"); let start = self.source_bytes_nth.get(); let end_exclusive = start.as_usize() + s.len(); - if let Some(b) = self.source.get((start.as_usize())..end_exclusive) { - if s == b { - match self.set_current_index(Utf8CharBoundaryStartByte::new(end_exclusive)) { - Ok(()) => Ok(Some(s)), - Err(OutOfRangeError { .. }) => Ok(None), - } - } else { - Ok(None) + self.source.get((start.as_usize())..end_exclusive).map_or(Ok(None), |b| if s == b { + match self.set_current_index(Utf8CharBoundaryStartByte::new(end_exclusive)) { + Ok(()) => Ok(Some(s)), + Err(OutOfRangeError { .. }) => Ok(None), } } else { Ok(None) - } + }) } #[allow(clippy::unnecessary_wraps)] @@ -235,6 +237,7 @@ impl Lexer<'_> { let s = unsafe { this.source.get_unchecked(index..(index + stride.as_usize())) }; + #[allow(clippy::or_fun_call)] // latter is fine because it does not cost let c = s.chars().next().ok_or(this.report_out_of_range_error())?; @@ -360,27 +363,30 @@ impl Lexer<'_> { if old < new { // forward let new_line = current_line + src[old..new].bytes().filter(|x| *x == b'\n').count(); - let new_col = if let Some(old_relative) = src[old..new].rfind('\n') { - // .......................OLD.................NEW - // |<--------N------>| - new - (old + old_relative) - } else { + let new_col = src[old..new].rfind('\n').map_or_else(|| { let mut c = self.column.get().get(); c += new - old; c - }; + }, |old_relative| { + new - (old + old_relative) + }); self.line.set(NonZeroUsize::new(new_line).expect("overflow")); self.column.set(NonZeroUsize::new(new_col).expect("overflow")); } else { // back let new_line = current_line - src[new..old].bytes().filter(|x| *x == b'\n').count(); - let new_col = if let Some(new_relative) = src[new..old].find('\n') { + let new_col = src[new..old].find('\n').map_or_else(|| { + let mut c = self.column.get().get(); + c += old - new; + + c + }, |new_relative| { // .......................NEW.................OLD // |<--------N------>| let nr = new + new_relative; - if let Some(most_recent_nl) = src[..nr].rfind('\n') { + src[..nr].rfind('\n').map_or(nr, |most_recent_nl| { // ..............NEW.................OLD // |<--------N------>| // |<-----MRN-------------->| @@ -389,15 +395,8 @@ impl Lexer<'_> { // cost on runtime. assert!(most_recent_nl < nr); nr - most_recent_nl - } else { - nr - } - } else { - let mut c = self.column.get().get(); - c += old - new; - - c - }; + }) + }); self.line.set(NonZeroUsize::new(new_line).expect("overflow")); self.column.set(NonZeroUsize::new(new_col).expect("overflow")); diff --git a/package/origlang-compiler/src/lexer/error.rs b/package/origlang-compiler/src/lexer/error.rs index b9c4fa8e..cfecdafd 100644 --- a/package/origlang-compiler/src/lexer/error.rs +++ b/package/origlang-compiler/src/lexer/error.rs @@ -21,6 +21,7 @@ pub enum LexerError { #[derive(Debug, Error, Eq, PartialEq)] #[error("lexer index overflow: {current:?} > {max}")] +#[allow(clippy::module_name_repetitions)] pub struct OutOfRangeError { pub current: Utf8CharBoundaryStartByte, pub max: usize, diff --git a/package/origlang-compiler/src/lexer/token.rs b/package/origlang-compiler/src/lexer/token.rs index e0ac0a5b..a85c0bc5 100644 --- a/package/origlang-compiler/src/lexer/token.rs +++ b/package/origlang-compiler/src/lexer/token.rs @@ -8,8 +8,8 @@ pub struct TemporalLexerUnwindToken { } impl TemporalLexerUnwindToken { - #[must_use] - pub fn new(reset_to: Utf8CharBoundaryStartByte) -> Self { + #[must_use = "call Self::reset to invoke"] + pub const fn new(reset_to: Utf8CharBoundaryStartByte) -> Self { Self { unwind_index: reset_to } diff --git a/package/origlang-compiler/src/parser.rs b/package/origlang-compiler/src/parser.rs index c8d2ea66..fffb76a5 100644 --- a/package/origlang-compiler/src/parser.rs +++ b/package/origlang-compiler/src/parser.rs @@ -692,9 +692,9 @@ impl Parser<'_> { debug!("{:?}", self.lexer.peek().data); if self.lexer.peek().data != Token::SymComma { break - } else { - self.lexer.next(); } + + self.lexer.next(); } debug!("type:tuple:accumulator = {vec:?}"); diff --git a/package/origlang-compiler/src/type_check.rs b/package/origlang-compiler/src/type_check.rs index b38783d0..8604936a 100644 --- a/package/origlang-compiler/src/type_check.rs +++ b/package/origlang-compiler/src/type_check.rs @@ -25,7 +25,7 @@ impl TryIntoTypeCheckedForm for Expression { type Success = TypedExpression; type Err = TypeCheckError; - #[allow(clippy::too_many_lines)] + #[allow(clippy::too_many_lines, clippy::cast_possible_truncation)] fn type_check(self, checker: &TypeChecker) -> Result { match self { Self::IntLiteral { value, suffix } => { @@ -380,10 +380,10 @@ fn desugar( } } -fn extract_pattern(checked: TypedExpression, pattern: &AtomicPattern, type_annotation: Option, checker: &TypeChecker) -> Result, TypeCheckError> { +fn extract_pattern(expr: TypedExpression, pattern: &AtomicPattern, type_annotation: Option, checker: &TypeChecker) -> Result, TypeCheckError> { let Some(type_name) = type_annotation else { // no annotations, just set its type (type-inference) from the expr - return handle_atomic_pattern(checked, pattern, checker) + return handle_atomic_pattern(expr, pattern, checker) }; let Ok(dest) = checker.lower_type_signature_into_type(&type_name) else { @@ -392,19 +392,19 @@ fn extract_pattern(checked: TypedExpression, pattern: &AtomicPattern, type_annot }) }; - match dest.is_assignable(&checked.actual_type()) { + match dest.is_assignable(&expr.actual_type()) { AssignableQueryAnswer::Yes => { - handle_atomic_pattern(checked, pattern, checker) + handle_atomic_pattern(expr, pattern, checker) }, AssignableQueryAnswer::PossibleIfCoerceSourceImplicitly => { Err(TypeCheckError::UnassignableType { - from: checked.actual_type(), + from: expr.actual_type(), to: dest, }) } AssignableQueryAnswer::No => { Err(TypeCheckError::UnassignableType { - from: checked.actual_type(), + from: expr.actual_type(), to: dest, }) } diff --git a/package/origlang-ir-optimizer/src/ir0.rs b/package/origlang-ir-optimizer/src/ir0.rs index 1b239a10..6c1faff5 100644 --- a/package/origlang-ir-optimizer/src/ir0.rs +++ b/package/origlang-ir-optimizer/src/ir0.rs @@ -3,6 +3,7 @@ use origlang_ir::IR0; pub struct EliminateAfterExit(pub Vec); impl EliminateAfterExit { + #[must_use = "return value is optimized IR, dropping it will waste it"] pub fn optimize(self) -> Vec { let mut x = self.0.into_iter().take_while(|x| *x != IR0::Exit).collect::>(); x.push(IR0::Exit); diff --git a/package/origlang-ir-optimizer/src/ir1.rs b/package/origlang-ir-optimizer/src/ir1.rs index ab33754c..227bc8a9 100644 --- a/package/origlang-ir-optimizer/src/ir1.rs +++ b/package/origlang-ir-optimizer/src/ir1.rs @@ -249,6 +249,7 @@ impl FoldBinaryOperatorInvocationWithConstant { pub struct FoldIfWithConstantCondition(pub Vec); impl FoldIfWithConstantCondition { + #[must_use = "return value is optimized IR, dropping it will waste it"] pub fn optimize(self) -> Vec { self.0.into_iter().map(|x| { match x { @@ -286,6 +287,7 @@ impl FoldIfWithConstantCondition { pub struct InlineSimpleBlock(pub Vec); impl InlineSimpleBlock { + #[must_use = "return value is optimized IR, dropping it will waste it"] pub fn optimize(self) -> Vec { self.0.into_iter().map(|x| match x { IR1::Output(x) => IR1::Output(Self::walk_expression(x)), diff --git a/package/origlang-ir-optimizer/src/preset.rs b/package/origlang-ir-optimizer/src/preset.rs index 74ca9d7b..5cf96174 100644 --- a/package/origlang-ir-optimizer/src/preset.rs +++ b/package/origlang-ir-optimizer/src/preset.rs @@ -6,6 +6,7 @@ pub trait OptimizationPreset { /// /// # Panics /// しăȘい。 + #[must_use = "return value is optimized IR, dropping it will waste it"] fn optimize(&self, seq: Vec) -> Vec; } @@ -13,7 +14,9 @@ pub trait OptimizationPreset { pub struct NoOptimization; impl OptimizationPreset for NoOptimization { + #[allow(clippy::redundant_closure_for_method_calls)] fn optimize(&self, seq: Vec) -> Vec { + #[allow(clippy::wildcard_imports)] use crate::ir0::*; seq @@ -40,7 +43,9 @@ impl OptimizationPreset for NoOptimization { pub struct SimpleOptimization; impl OptimizationPreset for SimpleOptimization { + #[allow(clippy::redundant_closure_for_method_calls)] fn optimize(&self, seq: Vec) -> Vec { + #[allow(clippy::wildcard_imports)] use crate::ir0::*; seq @@ -49,7 +54,9 @@ impl OptimizationPreset for SimpleOptimization { } impl OptimizationPreset for SimpleOptimization { + #[allow(clippy::redundant_closure_for_method_calls)] fn optimize(&self, seq: Vec) -> Vec { + #[allow(clippy::wildcard_imports)] use crate::ir1::*; seq diff --git a/package/origlang-ir/src/lib.rs b/package/origlang-ir/src/lib.rs index b55515c1..71e3fa9b 100644 --- a/package/origlang-ir/src/lib.rs +++ b/package/origlang-ir/src/lib.rs @@ -34,15 +34,8 @@ impl IntoVerbatimSequencedIR for TypedStatement { IR0::Normal(IR1::Output(expression)) ] } - Self::VariableDeclaration { identifier, expression } => { - vec![ - IR0::Normal(IR1::UpdateVariable { - ident: identifier, - value: expression, - }) - ] - } - Self::VariableAssignment { identifier, expression } => { + Self::VariableDeclaration { identifier, expression } + | Self::VariableAssignment { identifier, expression } => { vec![ IR0::Normal(IR1::UpdateVariable { ident: identifier, diff --git a/package/origlang-runtime/src/invoke_once.rs b/package/origlang-runtime/src/invoke_once.rs index 7369f500..fade35b2 100644 --- a/package/origlang-runtime/src/invoke_once.rs +++ b/package/origlang-runtime/src/invoke_once.rs @@ -11,7 +11,7 @@ pub struct InvokeOnce { } impl InvokeOnce { - pub fn new(value: T) -> Self { + pub const fn new(value: T) -> Self { Self { inner: RefCell::new(MaybeUninit::new(value)), owned: Cell::new(true), diff --git a/package/origlang-runtime/src/lib.rs b/package/origlang-runtime/src/lib.rs index d601c71f..356fd239 100644 --- a/package/origlang-runtime/src/lib.rs +++ b/package/origlang-runtime/src/lib.rs @@ -5,7 +5,7 @@ mod invoke_once; use std::cell::RefCell; use std::collections::{HashMap, VecDeque}; -use std::fmt::{Debug, Display, Formatter, Write}; +use std::fmt::{Debug, Display, Formatter}; use derive_more::{Display, From}; use log::debug; use tap::Conv; @@ -440,11 +440,10 @@ impl CanBeEvaluated for CompiledTypedExpression { let x = expr.evaluate(runtime)?; match x { TypeBox::Tuple(x) => { - if let Some(x) = x.boxes.get(*index) { - Ok(x.clone()) - } else { - indicate_type_checker_bug!(context = "tuple_destruction: out of bounds") - } + x.boxes.get(*index).map_or_else( + || indicate_type_checker_bug!(context = "tuple_destruction: out of bounds"), + |x| Ok(x.clone()) + ) } _other => indicate_type_checker_bug!(context = "must be tuple") } diff --git a/package/origlang-typesystem-model/src/lib.rs b/package/origlang-typesystem-model/src/lib.rs index 6235da6d..bb4136e9 100644 --- a/package/origlang-typesystem-model/src/lib.rs +++ b/package/origlang-typesystem-model/src/lib.rs @@ -96,7 +96,7 @@ impl Type { } } - #[must_use] pub fn as_tuple(&self) -> Option<&DisplayTupleType> { + #[must_use] pub const fn as_tuple(&self) -> Option<&DisplayTupleType> { if let Self::Tuple(x) = self { Some(x) } else { @@ -183,9 +183,7 @@ impl TypedExpression { Self::StringLiteral(_) => Type::String, Self::UnitLiteral => Type::Unit, Self::Variable { tp, .. } => tp.clone(), - Self::BinaryOperator { return_type, .. } => return_type.clone(), - Self::If { return_type, .. } => return_type.clone(), - Self::Block { return_type, .. } => return_type.clone(), + Self::BinaryOperator { return_type, .. } | Self::If { return_type, .. } | Self::Block { return_type, .. } => return_type.clone(), Self::Tuple { expressions } => Type::tuple(expressions.iter().map(Self::actual_type).collect()), Self::ExtractTuple { expr, index } => { expr.actual_type().as_tuple().map(|y| y.0[*index].clone()).expect("the underlying expression must be tuple and index must be within its bound")