Skip to content

Commit bbf4cbc

Browse files
committed
feat: add support for unsigned integer MIN and MAX type bounds
Introduce parser support for unsigned integer constants (MIN, MAX) across all variants. Can be further extended easily to include other type specific constants like ZERO, ONE in future.
1 parent 0445ef9 commit bbf4cbc

File tree

2 files changed

+248
-7
lines changed

2 files changed

+248
-7
lines changed

src/ast.rs

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use simplicity::jet::Elements;
1010

1111
use crate::debug::{CallTracker, DebugSymbols, TrackedCallName};
1212
use crate::error::{Error, RichError, Span, WithSpan};
13-
use crate::num::{NonZeroPow2Usize, Pow2Usize};
13+
use crate::num::{NonZeroPow2Usize, Pow2Usize, U256};
1414
use crate::parse::MatchPattern;
1515
use crate::pattern::Pattern;
1616
use crate::str::{AliasName, FunctionName, Identifier, ModuleName, WitnessName};
@@ -957,6 +957,77 @@ impl AbstractSyntaxTree for SingleExpression {
957957
let value = Value::parse_hexadecimal(bytes, ty).with_span(from)?;
958958
SingleExpressionInner::Constant(value)
959959
}
960+
parse::SingleExpressionInner::TypeBound(type_bound) => match type_bound {
961+
parse::TypeBound::UInt(uint_ty, bound) => {
962+
let int_ty = ty
963+
.as_integer()
964+
.ok_or(Error::ExpressionUnexpectedType(ty.clone()))
965+
.with_span(from)?;
966+
967+
if int_ty != *uint_ty {
968+
return Err(Error::ExpressionTypeMismatch(
969+
ty.clone(),
970+
ResolvedType::from(*uint_ty),
971+
))
972+
.with_span(from);
973+
}
974+
975+
let value = match (uint_ty, bound) {
976+
(UIntType::U1, parse::UIntBound::Min) => Value::from(UIntValue::U1(0)),
977+
(UIntType::U1, parse::UIntBound::Max) => Value::from(UIntValue::U1(1)),
978+
979+
(UIntType::U2, parse::UIntBound::Min) => Value::from(UIntValue::U2(0)),
980+
(UIntType::U2, parse::UIntBound::Max) => Value::from(UIntValue::U2(3)),
981+
982+
(UIntType::U4, parse::UIntBound::Min) => Value::from(UIntValue::U4(0)),
983+
(UIntType::U4, parse::UIntBound::Max) => Value::from(UIntValue::U4(15)),
984+
985+
(UIntType::U8, parse::UIntBound::Min) => {
986+
Value::from(UIntValue::U8(u8::MIN))
987+
}
988+
(UIntType::U8, parse::UIntBound::Max) => {
989+
Value::from(UIntValue::U8(u8::MAX))
990+
}
991+
992+
(UIntType::U16, parse::UIntBound::Min) => {
993+
Value::from(UIntValue::U16(u16::MIN))
994+
}
995+
(UIntType::U16, parse::UIntBound::Max) => {
996+
Value::from(UIntValue::U16(u16::MAX))
997+
}
998+
999+
(UIntType::U32, parse::UIntBound::Min) => {
1000+
Value::from(UIntValue::U32(u32::MIN))
1001+
}
1002+
(UIntType::U32, parse::UIntBound::Max) => {
1003+
Value::from(UIntValue::U32(u32::MAX))
1004+
}
1005+
1006+
(UIntType::U64, parse::UIntBound::Min) => {
1007+
Value::from(UIntValue::U64(u64::MIN))
1008+
}
1009+
(UIntType::U64, parse::UIntBound::Max) => {
1010+
Value::from(UIntValue::U64(u64::MAX))
1011+
}
1012+
1013+
(UIntType::U128, parse::UIntBound::Min) => {
1014+
Value::from(UIntValue::U128(u128::MIN))
1015+
}
1016+
(UIntType::U128, parse::UIntBound::Max) => {
1017+
Value::from(UIntValue::U128(u128::MAX))
1018+
}
1019+
1020+
(UIntType::U256, parse::UIntBound::Min) => {
1021+
Value::from(UIntValue::U256(U256::MIN))
1022+
}
1023+
(UIntType::U256, parse::UIntBound::Max) => {
1024+
Value::from(UIntValue::U256(U256::MAX))
1025+
}
1026+
};
1027+
1028+
SingleExpressionInner::Constant(value)
1029+
}
1030+
},
9601031
parse::SingleExpressionInner::Witness(name) => {
9611032
scope
9621033
.insert_witness(name.clone(), ty.clone())

src/parse.rs

Lines changed: 176 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,28 @@ impl TypeAlias {
249249

250250
impl_eq_hash!(TypeAlias; name, ty);
251251

252+
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
253+
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
254+
pub enum UIntBound {
255+
Min,
256+
Max,
257+
}
258+
259+
impl UIntBound {
260+
pub const fn as_str(self) -> &'static str {
261+
match self {
262+
Self::Min => "MIN",
263+
Self::Max => "MAX",
264+
}
265+
}
266+
}
267+
268+
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
269+
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
270+
pub enum TypeBound {
271+
UInt(UIntType, UIntBound),
272+
}
273+
252274
/// An expression is something that returns a value.
253275
#[derive(Clone, Debug)]
254276
pub struct Expression {
@@ -339,6 +361,8 @@ pub enum SingleExpressionInner {
339361
Binary(Binary),
340362
/// Hexadecimal string literal.
341363
Hexadecimal(Hexadecimal),
364+
/// Constants of a type (e.g. MAX, MIN)
365+
TypeBound(TypeBound),
342366
/// Witness value.
343367
Witness(WitnessName),
344368
/// Parameter value.
@@ -633,9 +657,8 @@ impl TreeLike for ExprTree<'_> {
633657
| S::Decimal(_)
634658
| S::Hexadecimal(_)
635659
| S::Variable(_)
636-
| S::Witness(_)
637-
| S::Parameter(_)
638-
| S::Option(None) => Tree::Nullary,
660+
| S::TypeBound(_) => Tree::Nullary,
661+
S::Witness(_) | S::Parameter(_) | S::Option(None) => Tree::Nullary,
639662
S::Option(Some(l))
640663
| S::Either(Either::Left(l))
641664
| S::Either(Either::Right(l))
@@ -684,6 +707,9 @@ impl fmt::Display for ExprTree<'_> {
684707
S::Decimal(decimal) => write!(f, "{decimal}")?,
685708
S::Hexadecimal(hexadecimal) => write!(f, "0x{hexadecimal}")?,
686709
S::Variable(name) => write!(f, "{name}")?,
710+
S::TypeBound(TypeBound::UInt(ty, bound)) => {
711+
write!(f, "{ty}::{}", bound.as_str())?
712+
}
687713
S::Witness(name) => write!(f, "witness::{name}")?,
688714
S::Parameter(name) => write!(f, "param::{name}")?,
689715
S::Option(None) => write!(f, "None")?,
@@ -1606,6 +1632,34 @@ impl SingleExpression {
16061632

16071633
let match_expr = Match::parser(expr.clone()).map(SingleExpressionInner::Match);
16081634

1635+
let type_bound = Identifier::parser()
1636+
.then_ignore(just(Token::DoubleColon))
1637+
.then(Identifier::parser())
1638+
.try_map(
1639+
|(lhs, rhs), span| match UIntType::from_str(lhs.as_inner()) {
1640+
Ok(ty) => Ok((ty, rhs)),
1641+
// this is a fall through, this error is not emitted
1642+
Err(_) => Err(Error::Grammar("not a type bound".into()).with_span(span)),
1643+
},
1644+
)
1645+
.validate(|(ty, rhs), e, emit| {
1646+
let bound = match rhs.as_inner() {
1647+
"MIN" => UIntBound::Min,
1648+
"MAX" => UIntBound::Max,
1649+
_ => {
1650+
// Send the error through here
1651+
emit.emit(
1652+
Error::Grammar(
1653+
"Expected `MIN` or `MAX` after unsigned integer type".into(),
1654+
)
1655+
.with_span(e.span()),
1656+
);
1657+
UIntBound::Min
1658+
}
1659+
};
1660+
SingleExpressionInner::TypeBound(TypeBound::UInt(ty, bound))
1661+
});
1662+
16091663
let variable = Identifier::parser().map(SingleExpressionInner::Variable);
16101664

16111665
// Expression delimeted by parentheses
@@ -1616,7 +1670,7 @@ impl SingleExpression {
16161670

16171671
choice((
16181672
left, right, some, none, boolean, match_expr, expression, list, array, tuple, call,
1619-
literal, variable,
1673+
literal, type_bound, variable,
16201674
))
16211675
.map_with(|inner, e| Self {
16221676
inner,
@@ -2193,7 +2247,10 @@ mod test {
21932247
let parse_program = Program::parse_from_str_with_errors(input, &mut error_handler);
21942248

21952249
assert!(parse_program.is_none());
2196-
assert!(ErrorCollector::to_string(&error_handler).contains("Expected '::', found ':'"));
2250+
let errors = ErrorCollector::to_string(&error_handler);
2251+
2252+
assert!(parse_program.is_none());
2253+
assert!(errors.contains("::"), "{errors}");
21972254
}
21982255

21992256
#[test]
@@ -2203,6 +2260,119 @@ mod test {
22032260
let parse_program = Program::parse_from_str_with_errors(input, &mut error_handler);
22042261

22052262
assert!(parse_program.is_none());
2206-
assert!(ErrorCollector::to_string(&error_handler).contains("Expected ';', found '::'"));
2263+
let errors = ErrorCollector::to_string(&error_handler);
2264+
2265+
assert!(parse_program.is_none());
2266+
assert!(errors.contains("::"), "{errors}");
2267+
}
2268+
2269+
#[test]
2270+
fn invalid_input_falls_through_type_bound_on_try_match() {
2271+
let input = "fn main() { let pk: Pubkey = witnes::PK; }";
2272+
let mut error_handler = ErrorCollector::new(Arc::from(input));
2273+
let parse_program = Program::parse_from_str_with_errors(input, &mut error_handler);
2274+
2275+
assert!(parse_program.is_none());
2276+
2277+
let error_str = ErrorCollector::to_string(&error_handler);
2278+
2279+
assert!(
2280+
error_str.contains("Expected ';', found '::'"),
2281+
"{}",
2282+
error_str
2283+
);
2284+
2285+
assert!(
2286+
!error_str.contains("Expected unsigned integer type before `::`"),
2287+
"{}",
2288+
error_str
2289+
);
2290+
2291+
assert!(
2292+
!error_str.contains("Expected `MIN` or `MAX` after unsigned integer type"),
2293+
"{}",
2294+
error_str
2295+
);
2296+
}
2297+
2298+
#[test]
2299+
fn valid_input_falls_through_type_bound_when_not_match() {
2300+
let input = "fn main() { let pk: Pubkey = Witness::PK; }";
2301+
let mut error_handler = ErrorCollector::new(Arc::from(input));
2302+
let parse_program = Program::parse_from_str_with_errors(input, &mut error_handler);
2303+
2304+
assert!(parse_program.is_none());
2305+
2306+
let error_str = ErrorCollector::to_string(&error_handler);
2307+
2308+
assert!(
2309+
error_str.contains("Expected ';', found '::'"),
2310+
"{}",
2311+
error_str
2312+
);
2313+
2314+
assert!(
2315+
!error_str.contains("Expected unsigned integer type before `::`"),
2316+
"{}",
2317+
error_str
2318+
);
2319+
2320+
assert!(
2321+
!error_str.contains("Expected `MIN` or `MAX` after unsigned integer type"),
2322+
"{}",
2323+
error_str
2324+
);
2325+
}
2326+
2327+
#[test]
2328+
fn parses_u8_min_max_invalid_reports_error() {
2329+
let input = "fn main() { let a: u8 = u8::MI; let b: u8 = u8::MA; }";
2330+
let mut error_handler = ErrorCollector::new(Arc::from(input));
2331+
let parse_program = Program::parse_from_str_with_errors(input, &mut error_handler);
2332+
let error_str = ErrorCollector::to_string(&error_handler);
2333+
2334+
assert!(parse_program.is_none());
2335+
2336+
assert!(
2337+
error_str.contains("Expected `MIN` or `MAX` after unsigned integer type"),
2338+
"{}",
2339+
error_str
2340+
);
2341+
}
2342+
2343+
#[test]
2344+
fn parses_u8_min_max() {
2345+
let src = "fn main() { let a: u8 = u8::MIN; let b: u8 = u8::MAX; }";
2346+
let program = Program::parse_from_str(src).expect("should parse");
2347+
let rendered = program.to_string();
2348+
assert!(rendered.contains("u8::MIN"));
2349+
assert!(rendered.contains("u8::MAX"));
2350+
}
2351+
2352+
#[test]
2353+
fn parses_all_uint_min_max() {
2354+
let types = ["u1", "u2", "u4", "u8", "u16", "u32", "u64", "u128", "u256"];
2355+
2356+
for ty in types {
2357+
let src = format!(
2358+
"fn main() {{ let a: {t} = {t}::MIN; let b: {t} = {t}::MAX; }}",
2359+
t = ty
2360+
);
2361+
2362+
let program = Program::parse_from_str(&src)
2363+
.unwrap_or_else(|e| panic!("failed to parse for {ty}: {e:?}"));
2364+
2365+
let rendered = program.to_string();
2366+
2367+
assert!(
2368+
rendered.contains(&format!("{ty}::MIN")),
2369+
"missing MIN for {ty}"
2370+
);
2371+
2372+
assert!(
2373+
rendered.contains(&format!("{ty}::MAX")),
2374+
"missing MAX for {ty}"
2375+
);
2376+
}
22072377
}
22082378
}

0 commit comments

Comments
 (0)