rukalang/elab/
runtime_elab.rs

1use super::helpers::resolve_local;
2use super::prelude::*;
3
4impl Elaborator {
5    pub(super) fn elaborate_block(
6        &mut self,
7        block: &mut Block,
8        expected_tail: Option<&Ty>,
9        locals: &mut Vec<(String, Ty)>,
10    ) -> Result<Ty, ElabError> {
11        let scope_start = locals.len();
12        let mut last_ty = Ty::Unit;
13        for index in 0..block.statements.len() {
14            let expected = if index + 1 == block.statements.len() {
15                expected_tail
16            } else {
17                None
18            };
19            last_ty = self.elaborate_stmt(&mut block.statements[index], expected, locals)?;
20        }
21        locals.truncate(scope_start);
22        Ok(last_ty)
23    }
24
25    pub(super) fn elaborate_stmt(
26        &mut self,
27        stmt: &mut Stmt,
28        expected: Option<&Ty>,
29        locals: &mut Vec<(String, Ty)>,
30    ) -> Result<Ty, ElabError> {
31        match stmt {
32            Stmt::Let { name, value, .. } => {
33                let ty = self.elaborate_expr(value, None, locals)?;
34                locals.push((name.clone(), ty));
35                Ok(Ty::Unit)
36            }
37            Stmt::Assign { target, value, .. } => {
38                let expected_ty = resolve_local(locals, &target.base)?;
39                let _ = self.elaborate_expr(value, Some(&expected_ty), locals)?;
40                Ok(Ty::Unit)
41            }
42            Stmt::If {
43                condition,
44                then_block,
45                else_block,
46            } => {
47                let _ = self.elaborate_expr(condition, Some(&Ty::Bool), locals)?;
48                let _ = self.elaborate_block(then_block, None, locals)?;
49                if let Some(else_block) = else_block {
50                    let _ = self.elaborate_block(else_block, None, locals)?;
51                }
52                Ok(Ty::Unit)
53            }
54            Stmt::For {
55                binding,
56                iterable,
57                body,
58            } => {
59                let iterable_ty = self.elaborate_expr(iterable, None, locals)?;
60                let item_ty = match iterable_ty {
61                    Ty::Array { item, .. } => *item,
62                    Ty::Slice(item) => *item,
63                    other => {
64                        return Err(ElabError::TypeMismatch {
65                            expected: Ty::Slice(Box::new(Ty::I64)),
66                            actual: other,
67                        });
68                    }
69                };
70                locals.push((binding.name.clone(), item_ty));
71                let _ = self.elaborate_block(body, None, locals)?;
72                let _ = locals.pop();
73                Ok(Ty::Unit)
74            }
75            Stmt::While { condition, body } => {
76                let _ = self.elaborate_expr(condition, Some(&Ty::Bool), locals)?;
77                let _ = self.elaborate_block(body, None, locals)?;
78                Ok(Ty::Unit)
79            }
80            Stmt::Match { value, arms } => {
81                let subject_ty = self.elaborate_expr(value, None, locals)?;
82                let (subject_enum, subject_args, option_item) = match subject_ty {
83                    Ty::Enum { name, args } => (name, args, None),
84                    Ty::Option(item) => ("Option".to_owned(), Vec::new(), Some(*item)),
85                    other => {
86                        return Err(ElabError::TypeMismatch {
87                            expected: Ty::Enum {
88                                name: "Enum".to_owned(),
89                                args: Vec::new(),
90                            },
91                            actual: other,
92                        });
93                    }
94                };
95                for arm in arms {
96                    let scope_start = locals.len();
97                    if let MatchPattern::Variant {
98                        enum_name,
99                        variant,
100                        binders,
101                    } = &arm.pattern
102                    {
103                        let pattern_enum = enum_name.as_deref().unwrap_or(subject_enum.as_str());
104                        let payload_tys = if pattern_enum == "Option" {
105                            match (variant.as_str(), &option_item) {
106                                ("None", Some(_)) => Vec::new(),
107                                ("Some", Some(item)) => vec![item.clone()],
108                                _ => {
109                                    return Err(ElabError::UnknownIdentifier {
110                                        name: format!("{pattern_enum}::{variant}"),
111                                    });
112                                }
113                            }
114                        } else {
115                            self.resolve_enum_variant_payload_tys(
116                                pattern_enum,
117                                &subject_args,
118                                variant,
119                            )?
120                        };
121                        for (binder, binder_ty) in binders.iter().zip(payload_tys.into_iter()) {
122                            if let Some(name) = binder {
123                                locals.push((name.name.clone(), binder_ty));
124                            }
125                        }
126                    }
127                    let _ = self.elaborate_block(&mut arm.body, None, locals)?;
128                    locals.truncate(scope_start);
129                }
130                Ok(Ty::Unit)
131            }
132            Stmt::Meta { .. } => Ok(Ty::Unit),
133            Stmt::Expr { expr, has_semi } => {
134                if *has_semi {
135                    let _ = self.elaborate_expr(expr, None, locals)?;
136                    Ok(Ty::Unit)
137                } else {
138                    self.elaborate_expr(expr, expected, locals)
139                }
140            }
141        }
142    }
143
144    pub(super) fn elaborate_expr(
145        &mut self,
146        expr: &mut Expr,
147        expected: Option<&Ty>,
148        locals: &mut Vec<(String, Ty)>,
149    ) -> Result<Ty, ElabError> {
150        match expr {
151            Expr::Ident { name, .. } => resolve_local(locals, name),
152            Expr::Int(value) => infer_number_literal_ty(value.to_string().as_str(), expected),
153            Expr::Number(value) => infer_number_literal_ty(value.raw.as_str(), expected),
154            Expr::String(_) => Ok(Ty::String),
155            Expr::Bool(_) => Ok(Ty::Bool),
156            Expr::Array { items, .. } => {
157                let expected_item = expected.and_then(|ty| match ty {
158                    Ty::Array { item, .. } => Some(item.as_ref()),
159                    Ty::Slice(item) => Some(item.as_ref()),
160                    _ => None,
161                });
162                if items.is_empty() {
163                    match expected {
164                        Some(Ty::Array { item, len: 0 }) => Ok(Ty::Array {
165                            item: Box::new((**item).clone()),
166                            len: 0,
167                        }),
168                        Some(Ty::Slice(item)) => Ok(Ty::Slice(Box::new((**item).clone()))),
169                        Some(other) => Err(ElabError::TypeMismatch {
170                            expected: other.clone(),
171                            actual: Ty::Array {
172                                item: Box::new(Ty::Unit),
173                                len: 0,
174                            },
175                        }),
176                        None => Err(ElabError::UnsupportedTypeExpr),
177                    }
178                } else {
179                    let first = self.elaborate_expr(&mut items[0], expected_item, locals)?;
180                    for item in &mut items[1..] {
181                        let next = self.elaborate_expr(item, Some(&first), locals)?;
182                        if next != first {
183                            return Err(ElabError::TypeMismatch {
184                                expected: first,
185                                actual: next,
186                            });
187                        }
188                    }
189                    if matches!(expected, Some(Ty::Slice(_))) {
190                        Ok(Ty::Slice(Box::new(first)))
191                    } else {
192                        Ok(Ty::Array {
193                            item: Box::new(first),
194                            len: items.len(),
195                        })
196                    }
197                }
198            }
199            Expr::Tuple { items, .. } => {
200                if items.is_empty() {
201                    return Ok(Ty::Unit);
202                }
203
204                let expected_items = expected.and_then(|ty| match ty {
205                    Ty::Tuple(expected_items) if expected_items.len() == items.len() => {
206                        Some(expected_items.as_slice())
207                    }
208                    _ => None,
209                });
210                let mut item_tys = Vec::with_capacity(items.len());
211                for (index, item) in items.iter_mut().enumerate() {
212                    let expected_item = expected_items.and_then(|tys| tys.get(index));
213                    item_tys.push(self.elaborate_expr(item, expected_item, locals)?);
214                }
215                Ok(Ty::Tuple(item_tys))
216            }
217            Expr::StructLit { name, fields, .. } => {
218                self.elaborate_struct_lit_expr(name, fields, expected, locals)
219            }
220            Expr::Call { callee, args, .. } => {
221                self.elaborate_call_expr(callee, args, expected, locals)
222            }
223            Expr::IntrinsicCall { name, args, .. } => {
224                self.elaborate_intrinsic_call_expr(name, args, locals)
225            }
226            Expr::Field { base, field } => {
227                let base_ty = self.elaborate_expr(base, None, locals)?;
228                self.resolve_struct_field_ty(&base_ty, field)
229            }
230            Expr::Index { base, index } => {
231                let base_ty = self.elaborate_expr(base, None, locals)?;
232                let index_ty = self.elaborate_expr(index, None, locals)?;
233                if !index_ty.is_integer() {
234                    return Err(ElabError::TypeMismatch {
235                        expected: Ty::I64,
236                        actual: index_ty,
237                    });
238                }
239                match base_ty {
240                    Ty::Array { item, .. } => Ok(*item),
241                    Ty::Slice(item) => Ok(*item),
242                    Ty::Pointer(inner) => match *inner {
243                        Ty::Array { item, .. } => Ok(*item),
244                        Ty::Slice(item) => Ok(*item),
245                        other => Err(ElabError::TypeMismatch {
246                            expected: Ty::Slice(Box::new(Ty::I64)),
247                            actual: other,
248                        }),
249                    },
250                    other => Err(ElabError::TypeMismatch {
251                        expected: Ty::Slice(Box::new(Ty::I64)),
252                        actual: other,
253                    }),
254                }
255            }
256            Expr::SliceRange { base, start, end } => {
257                let base_ty = self.elaborate_expr(base, None, locals)?;
258                if let Some(start) = start {
259                    let start_ty = self.elaborate_expr(start, None, locals)?;
260                    if !start_ty.is_integer() {
261                        return Err(ElabError::TypeMismatch {
262                            expected: Ty::I64,
263                            actual: start_ty,
264                        });
265                    }
266                }
267                if let Some(end) = end {
268                    let end_ty = self.elaborate_expr(end, None, locals)?;
269                    if !end_ty.is_integer() {
270                        return Err(ElabError::TypeMismatch {
271                            expected: Ty::I64,
272                            actual: end_ty,
273                        });
274                    }
275                }
276                let item = match base_ty {
277                    Ty::Array { item, .. } => item,
278                    Ty::Slice(item) => item,
279                    Ty::Pointer(inner) => match *inner {
280                        Ty::Array { item, .. } => item,
281                        Ty::Slice(item) => item,
282                        other => {
283                            return Err(ElabError::TypeMismatch {
284                                expected: Ty::Slice(Box::new(Ty::I64)),
285                                actual: other,
286                            });
287                        }
288                    },
289                    other => {
290                        return Err(ElabError::TypeMismatch {
291                            expected: Ty::Slice(Box::new(Ty::I64)),
292                            actual: other,
293                        });
294                    }
295                };
296                Ok(Ty::Slice(item))
297            }
298            Expr::Prefix { op, value } => match op {
299                PrefixOp::Neg => {
300                    let value_ty = self.elaborate_expr(value, None, locals)?;
301                    if !is_signed_numeric(&value_ty) {
302                        return Err(ElabError::TypeMismatch {
303                            expected: Ty::I64,
304                            actual: value_ty,
305                        });
306                    }
307                    Ok(value_ty)
308                }
309                PrefixOp::Deref => {
310                    let value_ty = self.elaborate_expr(value, None, locals)?;
311                    match value_ty {
312                        Ty::Pointer(item) => Ok(*item),
313                        other => Err(ElabError::TypeMismatch {
314                            expected: Ty::Pointer(Box::new(Ty::Unit)),
315                            actual: other,
316                        }),
317                    }
318                }
319                _ => self.elaborate_expr(value, expected, locals),
320            },
321            Expr::Binary { lhs, rhs, .. } => {
322                let lhs_ty = self.elaborate_expr(lhs, None, locals)?;
323                let rhs_ty = self.elaborate_expr(rhs, None, locals)?;
324                let Some(result_ty) = resolve_numeric_result_ty(&lhs_ty, &rhs_ty, expected) else {
325                    return Err(ElabError::TypeMismatch {
326                        expected: Ty::I64,
327                        actual: if lhs_ty != Ty::I64 { lhs_ty } else { rhs_ty },
328                    });
329                };
330                Ok(result_ty)
331            }
332            Expr::Relational { lhs, rhs, .. } => {
333                let lhs_ty = self.elaborate_expr(lhs, None, locals)?;
334                let rhs_ty = self.elaborate_expr(rhs, None, locals)?;
335                if resolve_numeric_result_ty(&lhs_ty, &rhs_ty, None).is_none() {
336                    return Err(ElabError::TypeMismatch {
337                        expected: Ty::I64,
338                        actual: if lhs_ty != Ty::I64 { lhs_ty } else { rhs_ty },
339                    });
340                }
341                Ok(Ty::Bool)
342            }
343            Expr::Block(block) => self.elaborate_block(block, expected, locals),
344            Expr::Splice(_) => Err(ElabError::UnsupportedTypeExpr),
345        }
346    }
347
348    pub(super) fn resolve_struct_field_ty(
349        &self,
350        base_ty: &Ty,
351        field: &str,
352    ) -> Result<Ty, ElabError> {
353        if let Ty::Pointer(item) = base_ty {
354            return self.resolve_struct_field_ty(item, field);
355        }
356        if let Ty::Tuple(items) = base_ty {
357            let index = field
358                .parse::<usize>()
359                .map_err(|_| ElabError::UnknownStructField {
360                    name: base_ty.to_string(),
361                    field: field.to_owned(),
362                })?;
363            return items
364                .get(index)
365                .cloned()
366                .ok_or_else(|| ElabError::UnknownStructField {
367                    name: base_ty.to_string(),
368                    field: field.to_owned(),
369                });
370        }
371        let Ty::Struct { name, args } = base_ty else {
372            return Err(ElabError::FieldAccessNotStruct {
373                actual: base_ty.clone(),
374            });
375        };
376        let template = self
377            .templates
378            .get(name)
379            .ok_or_else(|| ElabError::UnknownTypeConstructor { name: name.clone() })?;
380        let field_schema = template
381            .fields
382            .iter()
383            .find(|entry| entry.name == field)
384            .ok_or_else(|| ElabError::UnknownStructField {
385                name: name.clone(),
386                field: field.to_owned(),
387            })?;
388        let mut bindings = BTreeMap::new();
389        for (param, arg) in template.type_params.iter().zip(args.iter()) {
390            bindings.insert(param.clone(), arg.clone());
391        }
392        let type_params = template
393            .type_params
394            .iter()
395            .cloned()
396            .collect::<BTreeSet<_>>();
397        self.resolve_type_expr(&field_schema.ty, &bindings, &type_params, None)
398    }
399
400    pub(super) fn resolve_enum_variant_payload_tys(
401        &self,
402        enum_name: &str,
403        enum_args: &[Ty],
404        variant: &str,
405    ) -> Result<Vec<Ty>, ElabError> {
406        let template = self.enum_templates.get(enum_name).ok_or_else(|| {
407            ElabError::UnknownTypeConstructor {
408                name: enum_name.to_owned(),
409            }
410        })?;
411        let payload =
412            template
413                .variants
414                .get(variant)
415                .ok_or_else(|| ElabError::UnknownIdentifier {
416                    name: format!("{enum_name}::{variant}"),
417                })?;
418
419        let mut bindings = BTreeMap::new();
420        for (param, arg) in template.type_params.iter().zip(enum_args.iter()) {
421            bindings.insert(param.clone(), arg.clone());
422        }
423        let type_params = template
424            .type_params
425            .iter()
426            .cloned()
427            .collect::<BTreeSet<_>>();
428
429        payload
430            .iter()
431            .map(|item| self.resolve_type_expr(item, &bindings, &type_params, None))
432            .collect()
433    }
434
435    pub(super) fn resolve_type_expr(
436        &self,
437        ty: &TypeExpr,
438        type_bindings: &BTreeMap<String, Ty>,
439        type_params: &BTreeSet<String>,
440        self_ty: Option<&Ty>,
441    ) -> Result<Ty, ElabError> {
442        match ty {
443            TypeExpr::TypeKind => Err(ElabError::UnsupportedTypeExpr),
444            TypeExpr::Pointer { item } => Ok(Ty::Pointer(Box::new(self.resolve_type_expr(
445                item,
446                type_bindings,
447                type_params,
448                self_ty,
449            )?))),
450            TypeExpr::Named(name) => match name.as_str() {
451                "Unit" => Ok(Ty::Unit),
452                "u8" => Ok(Ty::U8),
453                "u16" => Ok(Ty::U16),
454                "u32" => Ok(Ty::U32),
455                "u64" => Ok(Ty::U64),
456                "i8" => Ok(Ty::I8),
457                "i16" => Ok(Ty::I16),
458                "i32" => Ok(Ty::I32),
459                "i64" => Ok(Ty::I64),
460                "f32" => Ok(Ty::F32),
461                "f64" => Ok(Ty::F64),
462                "String" => Ok(Ty::String),
463                "Bool" => Ok(Ty::Bool),
464                "Option" => Err(ElabError::MissingTypeArgs { name: name.clone() }),
465                _ => {
466                    if type_params.contains(name) {
467                        type_bindings.get(name).cloned().ok_or_else(|| {
468                            ElabError::StructTypeArgsNotInferred { name: name.clone() }
469                        })
470                    } else if let Some(template) = self.templates.get(name) {
471                        if template.type_params.is_empty() {
472                            Ok(Ty::Struct {
473                                name: name.clone(),
474                                args: Vec::new(),
475                            })
476                        } else {
477                            Err(ElabError::MissingTypeArgs { name: name.clone() })
478                        }
479                    } else if let Some(template) = self.enum_templates.get(name) {
480                        if template.type_params.is_empty() {
481                            Ok(Ty::Enum {
482                                name: name.clone(),
483                                args: Vec::new(),
484                            })
485                        } else {
486                            Err(ElabError::MissingTypeArgs { name: name.clone() })
487                        }
488                    } else {
489                        Err(ElabError::UnknownTypeConstructor { name: name.clone() })
490                    }
491                }
492            },
493            TypeExpr::Array { item, len } => Ok(Ty::Array {
494                item: Box::new(self.resolve_type_expr(
495                    item,
496                    type_bindings,
497                    type_params,
498                    self_ty,
499                )?),
500                len: *len,
501            }),
502            TypeExpr::Slice { item } => Ok(Ty::Slice(Box::new(self.resolve_type_expr(
503                item,
504                type_bindings,
505                type_params,
506                self_ty,
507            )?))),
508            TypeExpr::Tuple(items) => {
509                if items.is_empty() {
510                    Ok(Ty::Unit)
511                } else {
512                    let mut resolved = Vec::with_capacity(items.len());
513                    for item in items {
514                        resolved.push(self.resolve_type_expr(
515                            item,
516                            type_bindings,
517                            type_params,
518                            self_ty,
519                        )?);
520                    }
521                    Ok(Ty::Tuple(resolved))
522                }
523            }
524            TypeExpr::Apply { callee, args } => {
525                if callee == "Option" {
526                    if args.len() != 1 {
527                        return Err(ElabError::TypeArgCountMismatch {
528                            name: callee.clone(),
529                            expected: 1,
530                            actual: args.len(),
531                        });
532                    }
533                    let item =
534                        self.resolve_type_expr(&args[0], type_bindings, type_params, self_ty)?;
535                    return Ok(Ty::Option(Box::new(item)));
536                }
537                if let Some(template) = self.templates.get(callee) {
538                    if args.len() != template.type_params.len() {
539                        return Err(ElabError::TypeArgCountMismatch {
540                            name: callee.clone(),
541                            expected: template.type_params.len(),
542                            actual: args.len(),
543                        });
544                    }
545                    let mut resolved_args = Vec::with_capacity(args.len());
546                    for arg in args {
547                        resolved_args.push(self.resolve_type_expr(
548                            arg,
549                            type_bindings,
550                            type_params,
551                            self_ty,
552                        )?);
553                    }
554                    Ok(Ty::Struct {
555                        name: callee.clone(),
556                        args: resolved_args,
557                    })
558                } else if let Some(template) = self.enum_templates.get(callee) {
559                    if args.len() != template.type_params.len() {
560                        return Err(ElabError::TypeArgCountMismatch {
561                            name: callee.clone(),
562                            expected: template.type_params.len(),
563                            actual: args.len(),
564                        });
565                    }
566                    let mut resolved_args = Vec::with_capacity(args.len());
567                    for arg in args {
568                        resolved_args.push(self.resolve_type_expr(
569                            arg,
570                            type_bindings,
571                            type_params,
572                            self_ty,
573                        )?);
574                    }
575                    Ok(Ty::Enum {
576                        name: callee.clone(),
577                        args: resolved_args,
578                    })
579                } else {
580                    Err(ElabError::UnknownTypeConstructor {
581                        name: callee.clone(),
582                    })
583                }
584            }
585            _ => Err(ElabError::UnsupportedTypeExpr),
586        }
587    }
588}
589
590fn infer_number_literal_ty(raw: &str, expected: Option<&Ty>) -> Result<Ty, ElabError> {
591    let cleaned = raw.replace('_', "");
592    let (core, suffix) = split_numeric_suffix(cleaned.as_str());
593    if core.contains('.') {
594        return match suffix {
595            Some("f32") => Ok(Ty::F32),
596            Some("f64") | None => Ok(Ty::F64),
597            _ => Err(ElabError::UnsupportedTypeExpr),
598        };
599    }
600    if let Some(suffix) = suffix {
601        return match suffix {
602            "u8" => Ok(Ty::U8),
603            "u16" => Ok(Ty::U16),
604            "u32" => Ok(Ty::U32),
605            "u64" => Ok(Ty::U64),
606            "i8" => Ok(Ty::I8),
607            "i16" => Ok(Ty::I16),
608            "i32" => Ok(Ty::I32),
609            "i64" => Ok(Ty::I64),
610            "f32" => Ok(Ty::F32),
611            "f64" => Ok(Ty::F64),
612            _ => Err(ElabError::UnsupportedTypeExpr),
613        };
614    }
615    if let Some(expected) = expected
616        && expected.is_numeric()
617    {
618        return Ok(expected.clone());
619    }
620    Ok(Ty::I64)
621}
622
623fn split_numeric_suffix(raw: &str) -> (&str, Option<&str>) {
624    const SUFFIXES: [&str; 10] = [
625        "u8", "u16", "u32", "u64", "i8", "i16", "i32", "i64", "f32", "f64",
626    ];
627    for suffix in SUFFIXES {
628        if let Some(core) = raw.strip_suffix(suffix) {
629            return (core, Some(suffix));
630        }
631    }
632    (raw, None)
633}
634
635fn is_signed_numeric(ty: &Ty) -> bool {
636    matches!(ty, Ty::I8 | Ty::I16 | Ty::I32 | Ty::I64 | Ty::F32 | Ty::F64)
637}
638
639fn integer_width(ty: &Ty) -> Option<u8> {
640    match ty {
641        Ty::U8 | Ty::I8 => Some(8),
642        Ty::U16 | Ty::I16 => Some(16),
643        Ty::U32 | Ty::I32 => Some(32),
644        Ty::U64 | Ty::I64 => Some(64),
645        _ => None,
646    }
647}
648
649fn is_signed_integer(ty: &Ty) -> bool {
650    matches!(ty, Ty::I8 | Ty::I16 | Ty::I32 | Ty::I64)
651}
652
653fn signed_ty_for_width(width: u8) -> Option<Ty> {
654    match width {
655        8 => Some(Ty::I8),
656        16 => Some(Ty::I16),
657        32 => Some(Ty::I32),
658        64 => Some(Ty::I64),
659        _ => None,
660    }
661}
662
663fn unsigned_ty_for_width(width: u8) -> Option<Ty> {
664    match width {
665        8 => Some(Ty::U8),
666        16 => Some(Ty::U16),
667        32 => Some(Ty::U32),
668        64 => Some(Ty::U64),
669        _ => None,
670    }
671}
672
673fn promote_numeric_types(lhs: &Ty, rhs: &Ty) -> Option<Ty> {
674    if !lhs.is_numeric() || !rhs.is_numeric() {
675        return None;
676    }
677    if lhs == rhs {
678        return Some(lhs.clone());
679    }
680    if lhs.is_float() || rhs.is_float() {
681        if matches!(lhs, Ty::F64) || matches!(rhs, Ty::F64) {
682            return Some(Ty::F64);
683        }
684        return Some(Ty::F32);
685    }
686    let lhs_w = integer_width(lhs)?;
687    let rhs_w = integer_width(rhs)?;
688    if is_signed_integer(lhs) == is_signed_integer(rhs) {
689        if is_signed_integer(lhs) {
690            return signed_ty_for_width(lhs_w.max(rhs_w));
691        }
692        return unsigned_ty_for_width(lhs_w.max(rhs_w));
693    }
694    let signed_w = if is_signed_integer(lhs) { lhs_w } else { rhs_w };
695    let unsigned_w = if is_signed_integer(lhs) { rhs_w } else { lhs_w };
696    if signed_w > unsigned_w {
697        return signed_ty_for_width(signed_w);
698    }
699    signed_ty_for_width(match unsigned_w {
700        8 => 16,
701        16 => 32,
702        32 => 64,
703        _ => return Some(Ty::F64),
704    })
705}
706
707fn resolve_numeric_result_ty(lhs: &Ty, rhs: &Ty, expected: Option<&Ty>) -> Option<Ty> {
708    if !lhs.is_numeric() || !rhs.is_numeric() {
709        return None;
710    }
711    if lhs.is_integer() && rhs.is_integer() {
712        return promote_numeric_types(lhs, rhs);
713    }
714    if let Some(expected) = expected
715        && expected.is_float()
716        && can_safely_coerce_numeric(lhs, expected)
717        && can_safely_coerce_numeric(rhs, expected)
718    {
719        return Some(expected.clone());
720    }
721    if lhs.is_float() && rhs.is_float() {
722        return Some(if matches!(lhs, Ty::F64) || matches!(rhs, Ty::F64) {
723            Ty::F64
724        } else {
725            Ty::F32
726        });
727    }
728    if can_safely_coerce_numeric(lhs, &Ty::F32) && can_safely_coerce_numeric(rhs, &Ty::F32) {
729        return Some(Ty::F32);
730    }
731    if can_safely_coerce_numeric(lhs, &Ty::F64) && can_safely_coerce_numeric(rhs, &Ty::F64) {
732        return Some(Ty::F64);
733    }
734    None
735}