Skip to content

Commit e6880a6

Browse files
authored
feat(typeck): contracts implicit conversions (#634)
Closes #619 - Added match arm (Contract(A), Contract(B)) that checks if A.linearized_bases (array of contract-ids of bases in order of inheritance) contains the contract ID of B - Also had to add the gcx parameter to 4 conversion functions (`convert_implicit_to`,`try_convert_implicit_to`, `try_convert_explicit_to`, `convert_explicit_to`) as the `try_convert_implicit_to` needs `gcx` to get `linearized_bases` by fetching `Contract` using id.
1 parent 75196c0 commit e6880a6

13 files changed

+200
-96
lines changed

crates/sema/src/ty/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ use interner::Interner;
4242

4343
#[allow(clippy::module_inception)]
4444
mod ty;
45-
pub use ty::{Ty, TyData, TyFlags, TyFnPtr, TyKind};
45+
pub use ty::{Ty, TyConvertError, TyData, TyFlags, TyFnPtr, TyKind};
4646

4747
type FxOnceMap<K, V> = once_map::OnceMap<K, V, FxBuildHasher>;
4848

crates/sema/src/ty/ty.rs

Lines changed: 79 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,39 @@ impl<'gcx> std::ops::Deref for Ty<'gcx> {
2525
}
2626
}
2727

28+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29+
pub enum TyConvertError {
30+
/// Generic incompatibility (fallback).
31+
Incompatible,
32+
33+
/// Contract doesn't inherit from target contract.
34+
NonDerivedContract,
35+
36+
/// Invalid conversion between types.
37+
InvalidConversion,
38+
}
39+
40+
impl TyConvertError {
41+
/// Returns the error message for this conversion error.
42+
pub fn message<'gcx>(self, from: Ty<'gcx>, to: Ty<'gcx>, gcx: Gcx<'gcx>) -> String {
43+
match self {
44+
Self::NonDerivedContract => {
45+
format!(
46+
"contract `{}` does not inherit from `{}`",
47+
from.display(gcx),
48+
to.display(gcx)
49+
)
50+
}
51+
Self::InvalidConversion => {
52+
format!("cannot convert `{}` to `{}`", from.display(gcx), to.display(gcx))
53+
}
54+
Self::Incompatible => {
55+
format!("expected `{}`, found `{}`", to.display(gcx), from.display(gcx))
56+
}
57+
}
58+
}
59+
}
60+
2861
impl<'gcx> Ty<'gcx> {
2962
pub fn new(gcx: Gcx<'gcx>, kind: TyKind<'gcx>) -> Self {
3063
gcx.mk_ty(kind)
@@ -382,12 +415,12 @@ impl<'gcx> Ty<'gcx> {
382415
pub fn common_type(self, b: Self, gcx: Gcx<'gcx>) -> Option<Self> {
383416
let a = self;
384417
if let Some(a) = a.mobile(gcx)
385-
&& b.convert_implicit_to(a)
418+
&& b.convert_implicit_to(a, gcx)
386419
{
387420
return Some(a);
388421
}
389422
if let Some(b) = b.mobile(gcx)
390-
&& a.convert_implicit_to(b)
423+
&& a.convert_implicit_to(b, gcx)
391424
{
392425
return Some(b);
393426
}
@@ -415,15 +448,18 @@ impl<'gcx> Ty<'gcx> {
415448
#[inline]
416449
#[doc(alias = "is_implicitly_convertible_to")]
417450
#[must_use]
418-
pub fn convert_implicit_to(self, other: Self) -> bool {
419-
self.try_convert_implicit_to(other).is_ok()
451+
pub fn convert_implicit_to(self, other: Self, gcx: Gcx<'gcx>) -> bool {
452+
self.try_convert_implicit_to(other, gcx).is_ok()
420453
}
421454

422455
/// Checks if the type is implicitly convertible to the given type.
423456
///
424457
/// See: <https://docs.soliditylang.org/en/latest/types.html#implicit-conversions>
425-
#[allow(clippy::result_unit_err)]
426-
pub fn try_convert_implicit_to(self, other: Self) -> Result<(), ()> {
458+
pub fn try_convert_implicit_to(
459+
self,
460+
other: Self,
461+
gcx: Gcx<'gcx>,
462+
) -> Result<(), TyConvertError> {
427463
use ElementaryType::*;
428464
use TyKind::*;
429465

@@ -442,29 +478,29 @@ impl<'gcx> Ty<'gcx> {
442478
// Same location: allowed if base types match.
443479
(DataLocation::Memory, DataLocation::Memory)
444480
| (DataLocation::Calldata, DataLocation::Calldata) => {
445-
from_inner.try_convert_implicit_to(to_inner)
481+
from_inner.try_convert_implicit_to(to_inner, gcx)
446482
}
447483

448484
// storage -> storage: allowed (reference assignment).
449485
(DataLocation::Storage, DataLocation::Storage) => {
450-
from_inner.try_convert_implicit_to(to_inner)
486+
from_inner.try_convert_implicit_to(to_inner, gcx)
451487
}
452488

453489
// calldata/storage -> memory: allowed (copy semantics).
454490
(DataLocation::Calldata, DataLocation::Memory)
455491
| (DataLocation::Storage, DataLocation::Memory) => {
456-
from_inner.try_convert_implicit_to(to_inner)
492+
from_inner.try_convert_implicit_to(to_inner, gcx)
457493
}
458494

459495
// memory/calldata -> storage: allowed (copy semantics).
460496
(DataLocation::Memory, DataLocation::Storage)
461497
| (DataLocation::Calldata, DataLocation::Storage) => {
462-
from_inner.try_convert_implicit_to(to_inner)
498+
from_inner.try_convert_implicit_to(to_inner, gcx)
463499
}
464500

465501
// storage -> calldata: never allowed.
466502
// memory -> calldata: never allowed.
467-
_ => Result::Err(()),
503+
_ => Result::Err(TyConvertError::Incompatible),
468504
}
469505
}
470506

@@ -480,12 +516,22 @@ impl<'gcx> Ty<'gcx> {
480516
{
481517
Ok(())
482518
} else {
483-
Result::Err(())
519+
Result::Err(TyConvertError::Incompatible)
520+
}
521+
}
522+
523+
// Contract -> Base contract (inheritance check)
524+
(Contract(self_contract_id), Contract(other_contract_id)) => {
525+
let self_contract = gcx.hir.contract(self_contract_id);
526+
if self_contract.linearized_bases.contains(&other_contract_id) {
527+
Ok(())
528+
} else {
529+
Result::Err(TyConvertError::NonDerivedContract)
484530
}
485531
}
486532

487533
// TODO: more implicit conversions
488-
_ => Result::Err(()),
534+
_ => Result::Err(TyConvertError::Incompatible),
489535
}
490536
}
491537

@@ -495,19 +541,22 @@ impl<'gcx> Ty<'gcx> {
495541
#[inline]
496542
#[doc(alias = "is_explicitly_convertible_to")]
497543
#[must_use]
498-
pub fn convert_explicit_to(self, other: Self) -> bool {
499-
self.try_convert_explicit_to(other).is_ok()
544+
pub fn convert_explicit_to(self, other: Self, gcx: Gcx<'gcx>) -> bool {
545+
self.try_convert_explicit_to(other, gcx).is_ok()
500546
}
501547

502548
/// Checks if the type is explicitly convertible to the given type.
503549
///
504550
/// See: <https://docs.soliditylang.org/en/latest/types.html#explicit-conversions>
505-
#[allow(clippy::result_unit_err)]
506-
pub fn try_convert_explicit_to(self, other: Self) -> Result<(), ()> {
551+
pub fn try_convert_explicit_to(
552+
self,
553+
other: Self,
554+
gcx: Gcx<'gcx>,
555+
) -> Result<(), TyConvertError> {
507556
use ElementaryType::*;
508557
use TyKind::*;
509558

510-
if self.try_convert_implicit_to(other).is_ok() {
559+
if self.try_convert_implicit_to(other, gcx).is_ok() {
511560
return Ok(());
512561
}
513562
match (self.kind, other.kind) {
@@ -522,7 +571,7 @@ impl<'gcx> Ty<'gcx> {
522571
if matches!(ty.kind, Elementary(Bytes)) {
523572
Ok(())
524573
} else {
525-
Result::Err(())
574+
Result::Err(TyConvertError::InvalidConversion)
526575
}
527576
}
528577

@@ -545,7 +594,17 @@ impl<'gcx> Ty<'gcx> {
545594
// address -> address payable.
546595
(Elementary(Address(false)), Elementary(Address(true))) => Ok(()),
547596

548-
_ => Result::Err(()),
597+
// Contract -> Base contract (inheritance check)
598+
(Contract(self_contract_id), Contract(other_contract_id)) => {
599+
let self_contract = gcx.hir.contract(self_contract_id);
600+
if self_contract.linearized_bases.contains(&other_contract_id) {
601+
Ok(())
602+
} else {
603+
Result::Err(TyConvertError::NonDerivedContract)
604+
}
605+
}
606+
607+
_ => Result::Err(TyConvertError::InvalidConversion),
549608
}
550609
}
551610

crates/sema/src/typeck/checker.rs

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -553,13 +553,11 @@ impl<'gcx> TypeChecker<'gcx> {
553553
);
554554
};
555555
let from = self.check_expr(from_expr);
556-
let Err(()) = from.try_convert_explicit_to(to) else { return to };
556+
let Err(err) = from.try_convert_explicit_to(to, self.gcx) else { return to };
557557

558-
let msg =
559-
format!("cannot convert `{}` to `{}`", from.display(self.gcx), to.display(self.gcx));
560-
let mut err = self.dcx().err(msg).span(span);
561-
err = err.span_label(span, "invalid explicit type conversion");
562-
self.gcx.mk_ty_err(err.emit())
558+
let mut diag = self.dcx().err("invalid explicit type conversion").span(span);
559+
diag = diag.span_label(span, err.message(from, to, self.gcx));
560+
self.gcx.mk_ty_err(diag.emit())
563561
}
564562

565563
#[track_caller]
@@ -569,18 +567,11 @@ impl<'gcx> TypeChecker<'gcx> {
569567
actual: Ty<'gcx>,
570568
expected: Ty<'gcx>,
571569
) {
572-
let Err(()) = actual.try_convert_implicit_to(expected) else { return };
573-
574-
let mut err = self.dcx().err("mismatched types").span(expr.span);
575-
err = err.span_label(
576-
expr.span,
577-
format!(
578-
"expected `{}`, found `{}`",
579-
expected.display(self.gcx),
580-
actual.display(self.gcx)
581-
),
582-
);
583-
err.emit();
570+
let Err(err) = actual.try_convert_implicit_to(expected, self.gcx) else { return };
571+
572+
let mut diag = self.dcx().err("mismatched types").span(expr.span);
573+
diag = diag.span_label(expr.span, err.message(actual, expected, self.gcx));
574+
diag.emit();
584575
}
585576

586577
#[must_use]

tests/ui/typeck/address_conversions_explicit.sol

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ contract C {
1616
address payable p2 = payable(p);
1717

1818
// Invalid conversions
19-
bytes32 b32_from_a = bytes32(a); //~ ERROR: cannot convert
20-
address a4 = address(b32); //~ ERROR: cannot convert
21-
uint256 u256_from_a = uint256(a); //~ ERROR: cannot convert
22-
address a5 = address(u256); //~ ERROR: cannot convert
19+
bytes32 b32_from_a = bytes32(a); //~ ERROR: invalid explicit type conversion
20+
address a4 = address(b32); //~ ERROR: invalid explicit type conversion
21+
uint256 u256_from_a = uint256(a); //~ ERROR: invalid explicit type conversion
22+
address a5 = address(u256); //~ ERROR: invalid explicit type conversion
2323
}
2424
}
Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,26 @@
1-
error: cannot convert `address` to `bytes32`
1+
error: invalid explicit type conversion
22
╭▸ ROOT/tests/ui/typeck/address_conversions_explicit.sol:LL:CC
33
44
LL │ bytes32 b32_from_a = bytes32(a);
5-
╰╴ ━━━━━━━━━━ invalid explicit type conversion
5+
╰╴ ━━━━━━━━━━ cannot convert `address` to `bytes32`
66

7-
error: cannot convert `bytes32` to `address`
7+
error: invalid explicit type conversion
88
╭▸ ROOT/tests/ui/typeck/address_conversions_explicit.sol:LL:CC
99
1010
LL │ address a4 = address(b32);
11-
╰╴ ━━━━━━━━━━━━ invalid explicit type conversion
11+
╰╴ ━━━━━━━━━━━━ cannot convert `bytes32` to `address`
1212

13-
error: cannot convert `address` to `uint256`
13+
error: invalid explicit type conversion
1414
╭▸ ROOT/tests/ui/typeck/address_conversions_explicit.sol:LL:CC
1515
1616
LL │ uint256 u256_from_a = uint256(a);
17-
╰╴ ━━━━━━━━━━ invalid explicit type conversion
17+
╰╴ ━━━━━━━━━━ cannot convert `address` to `uint256`
1818

19-
error: cannot convert `uint256` to `address`
19+
error: invalid explicit type conversion
2020
╭▸ ROOT/tests/ui/typeck/address_conversions_explicit.sol:LL:CC
2121
2222
LL │ address a5 = address(u256);
23-
╰╴ ━━━━━━━━━━━━━ invalid explicit type conversion
23+
╰╴ ━━━━━━━━━━━━━ cannot convert `uint256` to `address`
2424

2525
error: aborting due to 4 previous errors
2626

tests/ui/typeck/explicit_dynamic_bytes_conversion.sol

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ contract C {
88
bytes16 b4 = bytes16(a0);
99

1010
// Invalid Dynamic bytes conversion
11-
bytes memory a1 = bytes(b4); //~ERROR: cannot convert `bytes16` to `bytes`
12-
bytes memory a2 = bytes(b3); //~ERROR: cannot convert `bytes10` to `bytes`
13-
bytes memory a3 = bytes(b2); //~ERROR: cannot convert `bytes2` to `bytes`
14-
bytes memory a4 = bytes(b1); //~ERROR: cannot convert `bytes1` to `bytes`
11+
bytes memory a1 = bytes(b4); //~ERROR: invalid explicit type conversion
12+
bytes memory a2 = bytes(b3); //~ERROR: invalid explicit type conversion
13+
bytes memory a3 = bytes(b2); //~ERROR: invalid explicit type conversion
14+
bytes memory a4 = bytes(b1); //~ERROR: invalid explicit type conversion
1515
}
1616
}
Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,26 @@
1-
error: cannot convert `bytes16` to `bytes`
1+
error: invalid explicit type conversion
22
╭▸ ROOT/tests/ui/typeck/explicit_dynamic_bytes_conversion.sol:LL:CC
33
44
LL │ bytes memory a1 = bytes(b4);
5-
╰╴ ━━━━━━━━━ invalid explicit type conversion
5+
╰╴ ━━━━━━━━━ cannot convert `bytes16` to `bytes`
66

7-
error: cannot convert `bytes10` to `bytes`
7+
error: invalid explicit type conversion
88
╭▸ ROOT/tests/ui/typeck/explicit_dynamic_bytes_conversion.sol:LL:CC
99
1010
LL │ bytes memory a2 = bytes(b3);
11-
╰╴ ━━━━━━━━━ invalid explicit type conversion
11+
╰╴ ━━━━━━━━━ cannot convert `bytes10` to `bytes`
1212

13-
error: cannot convert `bytes2` to `bytes`
13+
error: invalid explicit type conversion
1414
╭▸ ROOT/tests/ui/typeck/explicit_dynamic_bytes_conversion.sol:LL:CC
1515
1616
LL │ bytes memory a3 = bytes(b2);
17-
╰╴ ━━━━━━━━━ invalid explicit type conversion
17+
╰╴ ━━━━━━━━━ cannot convert `bytes2` to `bytes`
1818

19-
error: cannot convert `bytes1` to `bytes`
19+
error: invalid explicit type conversion
2020
╭▸ ROOT/tests/ui/typeck/explicit_dynamic_bytes_conversion.sol:LL:CC
2121
2222
LL │ bytes memory a4 = bytes(b1);
23-
╰╴ ━━━━━━━━━ invalid explicit type conversion
23+
╰╴ ━━━━━━━━━ cannot convert `bytes1` to `bytes`
2424

2525
error: aborting due to 4 previous errors
2626

tests/ui/typeck/explicit_enum_conversion.sol

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,16 @@ contract C {
2222
}
2323

2424
function invalidEnumToBytes(TrafficLight t) public pure {
25-
bytes1 b1 = bytes1(t); //~ ERROR: cannot convert `enum C.TrafficLight` to `bytes1`
26-
bytes32 b32 = bytes32(t); //~ ERROR: cannot convert `enum C.TrafficLight` to `bytes32`
25+
bytes1 b1 = bytes1(t); //~ ERROR: invalid explicit type conversion
26+
bytes32 b32 = bytes32(t); //~ ERROR: invalid explicit type conversion
2727
}
2828

2929
function invalidEnumToAddress(TrafficLight t) public pure {
30-
address addr = address(t); //~ ERROR: cannot convert `enum C.TrafficLight` to `address`
30+
address addr = address(t); //~ ERROR: invalid explicit type conversion
3131
}
3232

3333
function invalidEnumToBool(TrafficLight t) public pure {
34-
bool b = bool(t); //~ ERROR: cannot convert `enum C.TrafficLight` to `bool`
34+
bool b = bool(t); //~ ERROR: invalid explicit type conversion
3535
}
3636
}
3737

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,26 @@
1-
error: cannot convert `enum C.TrafficLight` to `bytes1`
1+
error: invalid explicit type conversion
22
╭▸ ROOT/tests/ui/typeck/explicit_enum_conversion.sol:LL:CC
33
44
LL │ bytes1 b1 = bytes1(t);
5-
╰╴ ━━━━━━━━━ invalid explicit type conversion
5+
╰╴ ━━━━━━━━━ cannot convert `enum C.TrafficLight` to `bytes1`
66

7-
error: cannot convert `enum C.TrafficLight` to `bytes32`
7+
error: invalid explicit type conversion
88
╭▸ ROOT/tests/ui/typeck/explicit_enum_conversion.sol:LL:CC
99
1010
LL │ bytes32 b32 = bytes32(t);
11-
╰╴ ━━━━━━━━━━ invalid explicit type conversion
11+
╰╴ ━━━━━━━━━━ cannot convert `enum C.TrafficLight` to `bytes32`
1212

13-
error: cannot convert `enum C.TrafficLight` to `address`
13+
error: invalid explicit type conversion
1414
╭▸ ROOT/tests/ui/typeck/explicit_enum_conversion.sol:LL:CC
1515
1616
LL │ address addr = address(t);
17-
╰╴ ━━━━━━━━━━ invalid explicit type conversion
17+
╰╴ ━━━━━━━━━━ cannot convert `enum C.TrafficLight` to `address`
1818

19-
error: cannot convert `enum C.TrafficLight` to `bool`
19+
error: invalid explicit type conversion
2020
╭▸ ROOT/tests/ui/typeck/explicit_enum_conversion.sol:LL:CC
2121
2222
LL │ bool b = bool(t);
23-
╰╴ ━━━━━━━ invalid explicit type conversion
23+
╰╴ ━━━━━━━ cannot convert `enum C.TrafficLight` to `bool`
2424

2525
error: aborting due to 4 previous errors
2626

0 commit comments

Comments
 (0)