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}