Skip to content

Commit

Permalink
Defer integer literal type resolution until after type checking (#711)
Browse files Browse the repository at this point in the history
* first attempt - draft

* Some cleanup

* Cleaning up literal.rs

* Cleaning up typed_expression.rs

* More cleanup and supporting trivial cases like let x = 8

* Fixing pest::span in error handling

* minor style changes

* minor style imporvements

* more minor changes

* debugging

* Fixing the struct case

* fixing arrays

* Adding a small test

* minor fixes

* addressing warnings

* Addressing sezna's comments

* CI

* Removing unnecessary pub keyword

* fmt

* Update test/src/e2e_vm_tests/test_programs/literal_too_large_for_type/Forc.toml

Co-authored-by: Alex Hansen <alex@alex-hansen.com>

Co-authored-by: Alex Hansen <alex@alex-hansen.com>
  • Loading branch information
mohammadfawaz and sezna authored Jan 28, 2022
1 parent bba0d64 commit 8fe74e0
Show file tree
Hide file tree
Showing 13 changed files with 204 additions and 44 deletions.
1 change: 1 addition & 0 deletions sway-core/src/asm_generation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,7 @@ impl fmt::Display for DataSection {
Literal::U16(num) => format!(".u16 {:#04x}", num),
Literal::U32(num) => format!(".u32 {:#04x}", num),
Literal::U64(num) => format!(".u64 {:#04x}", num),
Literal::Numeric(num) => format!(".u64 {:#04x}", num),
Literal::Boolean(b) => format!(".bool {}", if *b { "0x01" } else { "0x00" }),
Literal::String(st) => format!(".str \"{}\"", st.as_str()),
Literal::Byte(b) => format!(".byte {:#08b}", b),
Expand Down
2 changes: 2 additions & 0 deletions sway-core/src/optimize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1345,6 +1345,7 @@ fn convert_literal_to_value(context: &mut Context, ast_literal: &Literal) -> Val
Literal::U16(n) => Constant::get_uint(context, 16, *n as u64),
Literal::U32(n) => Constant::get_uint(context, 32, *n as u64),
Literal::U64(n) => Constant::get_uint(context, 64, *n),
Literal::Numeric(n) => Constant::get_uint(context, 64, *n),
Literal::String(s) => Constant::get_string(context, s.as_str().to_owned()),
Literal::Boolean(b) => Constant::get_bool(context, *b),
Literal::B256(bs) => Constant::get_b256(context, *bs),
Expand All @@ -1357,6 +1358,7 @@ fn convert_literal_to_constant(ast_literal: &Literal) -> Constant {
Literal::U16(n) => Constant::new_uint(16, *n as u64),
Literal::U32(n) => Constant::new_uint(32, *n as u64),
Literal::U64(n) => Constant::new_uint(64, *n),
Literal::Numeric(n) => Constant::new_uint(64, *n),
Literal::String(s) => Constant::new_string(s.as_str().to_owned()),
Literal::Boolean(b) => Constant::new_bool(*b),
Literal::B256(bs) => Constant::new_b256(*bs),
Expand Down
90 changes: 58 additions & 32 deletions sway-core/src/parse_tree/literal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub enum Literal {
U32(u32),
U64(u64),
String(span::Span),
Numeric(u64),
Boolean(bool),
Byte(u8),
B256([u8; 32]),
Expand All @@ -36,6 +37,7 @@ impl Literal {
U16(_) => ResolvedType::UnsignedInteger(IntegerBits::Sixteen),
U32(_) => ResolvedType::UnsignedInteger(IntegerBits::ThirtyTwo),
U64(_) => ResolvedType::UnsignedInteger(IntegerBits::SixtyFour),
Numeric(_) => ResolvedType::UnsignedInteger(IntegerBits::SixtyFour),
String(inner) => ResolvedType::Str(inner.as_str().len() as u64),
Boolean(_) => ResolvedType::Boolean,
Byte(_) => ResolvedType::Byte,
Expand All @@ -49,7 +51,30 @@ impl Literal {
let path = config.map(|c| c.path());
let lit_inner = lit.into_inner().next().unwrap();
let (parsed, span): (Result<Literal, CompileError>, _) = match lit_inner.as_rule() {
Rule::integer => {
Rule::basic_integer => {
let span = span::Span {
span: lit_inner.as_span(),
path: path.clone(),
};
(
lit_inner
.as_str()
.trim()
.replace("_", "")
.parse()
.map(Literal::Numeric)
.map_err(|e| {
Literal::handle_parse_int_error(
e,
TypeInfo::UnsignedInteger(IntegerBits::SixtyFour),
lit_inner.as_span(),
path.clone(),
)
}),
span,
)
}
Rule::typed_integer => {
let mut int_inner = lit_inner.into_inner().next().unwrap();
let rule = int_inner.as_rule();
if int_inner.as_rule() != Rule::basic_integer {
Expand All @@ -68,7 +93,7 @@ impl Literal {
.parse()
.map(Literal::U8)
.map_err(|e| {
handle_parse_int_error(
Literal::handle_parse_int_error(
e,
TypeInfo::UnsignedInteger(IntegerBits::Eight),
int_inner.as_span(),
Expand All @@ -82,7 +107,7 @@ impl Literal {
.parse()
.map(Literal::U16)
.map_err(|e| {
handle_parse_int_error(
Literal::handle_parse_int_error(
e,
TypeInfo::UnsignedInteger(IntegerBits::Sixteen),
int_inner.as_span(),
Expand All @@ -96,7 +121,7 @@ impl Literal {
.parse()
.map(Literal::U32)
.map_err(|e| {
handle_parse_int_error(
Literal::handle_parse_int_error(
e,
TypeInfo::UnsignedInteger(IntegerBits::ThirtyTwo),
int_inner.as_span(),
Expand All @@ -110,7 +135,7 @@ impl Literal {
.parse()
.map(Literal::U64)
.map_err(|e| {
handle_parse_int_error(
Literal::handle_parse_int_error(
e,
TypeInfo::UnsignedInteger(IntegerBits::SixtyFour),
int_inner.as_span(),
Expand Down Expand Up @@ -210,6 +235,7 @@ impl Literal {
vec![0, 0, 0, 0, bytes[0], bytes[1], bytes[2], bytes[3]]
}
U64(val) => val.to_be_bytes().to_vec(),
Numeric(val) => val.to_be_bytes().to_vec(),
Boolean(b) => {
vec![
0,
Expand Down Expand Up @@ -241,6 +267,33 @@ impl Literal {
pub(crate) fn new_pointer_literal(offset_bytes: u64) -> Literal {
Literal::U64(offset_bytes)
}

#[allow(clippy::wildcard_in_or_patterns)]
pub(crate) fn handle_parse_int_error(
e: ParseIntError,
ty: TypeInfo,
span: Span,
path: Option<Arc<PathBuf>>,
) -> CompileError {
match e.kind() {
IntErrorKind::PosOverflow => CompileError::IntegerTooLarge {
ty: ty.friendly_type_str(),
span: span::Span { span, path },
},
IntErrorKind::NegOverflow => CompileError::IntegerTooSmall {
ty: ty.friendly_type_str(),
span: span::Span { span, path },
},
IntErrorKind::InvalidDigit => CompileError::IntegerContainsInvalidDigit {
ty: ty.friendly_type_str(),
span: span::Span { span, path },
},
IntErrorKind::Zero | IntErrorKind::Empty | _ => CompileError::Internal(
"Called incorrect internal sway-core on literal type.",
span::Span { span, path },
),
}
}
}

fn parse_hex_from_pair(
Expand Down Expand Up @@ -366,30 +419,3 @@ fn parse_binary_from_pair(
}
})
}

#[allow(clippy::wildcard_in_or_patterns)]
fn handle_parse_int_error(
e: ParseIntError,
ty: TypeInfo,
span: Span,
path: Option<Arc<PathBuf>>,
) -> CompileError {
match e.kind() {
IntErrorKind::PosOverflow => CompileError::IntegerTooLarge {
ty: ty.friendly_type_str(),
span: span::Span { span, path },
},
IntErrorKind::NegOverflow => CompileError::IntegerTooSmall {
ty: ty.friendly_type_str(),
span: span::Span { span, path },
},
IntErrorKind::InvalidDigit => CompileError::IntegerContainsInvalidDigit {
ty: ty.friendly_type_str(),
span: span::Span { span, path },
},
IntErrorKind::Zero | IntErrorKind::Empty | _ => CompileError::Internal(
"Called incorrect internal sway-core on literal type.",
span::Span { span, path },
),
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -315,10 +315,34 @@ impl TypedExpression {
typed_expression.return_type = namespace
.resolve_type_with_self(look_up_type_id(typed_expression.return_type), self_type)
.unwrap_or_else(|_| {
errors.push(CompileError::UnknownType { span: expr_span });
errors.push(CompileError::UnknownType {
span: expr_span.clone(),
});
insert_type(TypeInfo::ErrorRecovery)
});

// Literals of type Numeric can now be resolved if typed_expression.return_type is
// an UnsignedInteger or a Numeric
if let TypedExpressionVariant::Literal(lit) = typed_expression.clone().expression {
if let Literal::Numeric(_) = lit {
match look_up_type_id(typed_expression.return_type) {
TypeInfo::UnsignedInteger(_) | TypeInfo::Numeric => {
typed_expression = check!(
Self::resolve_numeric_literal(
lit,
expr_span,
typed_expression.return_type
),
return err(warnings, errors),
warnings,
errors
)
}
_ => {}
}
}
}

ok(typed_expression, warnings, errors)
}

Expand All @@ -338,6 +362,7 @@ impl TypedExpression {
fn type_check_literal(lit: Literal, span: Span) -> CompileResult<TypedExpression> {
let return_type = match &lit {
Literal::String(s) => TypeInfo::Str(s.as_str().len() as u64),
Literal::Numeric(_) => TypeInfo::Numeric,
Literal::U8(_) => TypeInfo::UnsignedInteger(IntegerBits::Eight),
Literal::U16(_) => TypeInfo::UnsignedInteger(IntegerBits::Sixteen),

Expand Down Expand Up @@ -1780,6 +1805,98 @@ impl TypedExpression {
}
}

fn resolve_numeric_literal(
lit: Literal,
span: Span,
new_type: TypeId,
) -> CompileResult<TypedExpression> {
let mut errors = vec![];
let pest_span = span.clone().span;
let path = span.clone().path;

// Parse and resolve a Numeric(span) based on new_type.
let (val, new_integer_type) = match lit {
Literal::Numeric(num) => match look_up_type_id(new_type) {
TypeInfo::UnsignedInteger(n) => match n {
IntegerBits::Eight => (
num.to_string().parse().map(Literal::U8).map_err(|e| {
Literal::handle_parse_int_error(
e,
TypeInfo::UnsignedInteger(IntegerBits::Eight),
pest_span,
path,
)
}),
new_type,
),
IntegerBits::Sixteen => (
num.to_string().parse().map(Literal::U16).map_err(|e| {
Literal::handle_parse_int_error(
e,
TypeInfo::UnsignedInteger(IntegerBits::Sixteen),
pest_span,
path,
)
}),
new_type,
),
IntegerBits::ThirtyTwo => (
num.to_string().parse().map(Literal::U32).map_err(|e| {
Literal::handle_parse_int_error(
e,
TypeInfo::UnsignedInteger(IntegerBits::ThirtyTwo),
pest_span,
path,
)
}),
new_type,
),
IntegerBits::SixtyFour => (
num.to_string().parse().map(Literal::U64).map_err(|e| {
Literal::handle_parse_int_error(
e,
TypeInfo::UnsignedInteger(IntegerBits::SixtyFour),
pest_span,
path,
)
}),
new_type,
),
},
TypeInfo::Numeric => (
num.to_string().parse().map(Literal::U64).map_err(|e| {
Literal::handle_parse_int_error(
e,
TypeInfo::UnsignedInteger(IntegerBits::SixtyFour),
pest_span,
path,
)
}),
insert_type(TypeInfo::UnsignedInteger(IntegerBits::SixtyFour)),
),
_ => unreachable!("Unexpected type for integer literals"),
},
_ => unreachable!("Unexpected non-integer literals"),
};

match val {
Ok(v) => {
let exp = TypedExpression {
expression: TypedExpressionVariant::Literal(v),
return_type: new_integer_type,
is_constant: IsConstant::Yes,
span,
};
ok(exp, vec![], vec![])
}
Err(e) => {
errors.push(e);
let exp = error_recovery_expr(span);
ok(exp, vec![], errors)
}
}
}

pub(crate) fn pretty_print(&self) -> String {
format!(
"{} ({})",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ impl TypedExpressionVariant {
Literal::U16(content) => content.to_string(),
Literal::U32(content) => content.to_string(),
Literal::U64(content) => content.to_string(),
Literal::Numeric(content) => content.to_string(),
Literal::String(content) => content.as_str().to_string(),
Literal::Boolean(content) => content.to_string(),
Literal::Byte(content) => content.to_string(),
Expand Down
13 changes: 6 additions & 7 deletions sway-core/src/sway.pest
Original file line number Diff line number Diff line change
Expand Up @@ -75,17 +75,16 @@ if_exp = {"if" ~ expr ~ code_block ~ ("else" ~ (code_block|if_exp))?}
op = {"+"|"-"|"/"|"*"|"=="|"!="|"<="|">="|"||"|"|"|"&&"|"&"|"^"|"%"|"<"|">"}
unary_op = {"!"|ref_keyword|deref_keyword}

literal_value = {integer|byte|string|boolean}
literal_value = {typed_integer|basic_integer|byte|string|boolean}

boolean = {true_keyword|false_keyword}
string = ${"\"" ~ char* ~ "\""}
integer = {(u8_integer|u16_integer|u32_integer|u64_integer)}
typed_integer = {(u8_integer|u16_integer|u32_integer|u64_integer)}
basic_integer = @{!("0b"|"0x") ~ ASCII_DIGIT ~ (ASCII_DIGIT|"_")*}
u8_integer = {basic_integer ~ "u8"}
u16_integer = {basic_integer ~ "u16"}
u32_integer = {basic_integer ~ "u32"}
// default is u64
u64_integer = {basic_integer ~ "u64"?}
u64_integer = {basic_integer ~ "u64"}
byte = {binary_byte|hex_byte}
binary_byte = @{"0b" ~ ("1"|"0"|"_")*}
hex_byte = @{"0x" ~ hex_digit*}
Expand Down Expand Up @@ -114,7 +113,7 @@ array_exp = {"[" ~ array_elems? ~ "]"}
// Strictly speaking the [val; count] initialiser for a static array can have any constant expression
// for the value and the count, but Sway doesn't yet have constant expression resolution, so for now
// we can use a literal and an integer.
array_elems = {literal_value ~ ";" ~ u64_integer|expr ~ ("," ~ expr)*}
array_elems = {literal_value ~ ";" ~ basic_integer|expr ~ ("," ~ expr)*}

// declarations
declaration = {(non_var_decl|var_decl|reassignment)}
Expand Down Expand Up @@ -157,7 +156,7 @@ str_type = { "str" ~ "[" ~ basic_integer ~ "]" }
trait_bounds = {"where" ~ (generic_type_param ~ ":" ~ trait_name) ~ ("," ~ generic_type_param ~ ":" ~ trait_name)*}
generic_type_param = {ident}
// Array size can be any constant u64 expression, but we don't properly support constant expressions. See `array_elems rule above.
array_type = {"[" ~ type_name ~ ";" ~ u64_integer~ "]"}
array_type = {"[" ~ type_name ~ ";" ~ basic_integer ~ "]"}

// statements
// // statements are basically non-expressions that don't alter the namespace like declarations do
Expand Down Expand Up @@ -187,7 +186,7 @@ asm_registers = {"(" ~ (asm_register_declaration ~ ("," ~ asm_regist
asm_register_declaration = {ident ~ (":" ~ expr)?}
asm_op = {opcode ~ (asm_immediate|asm_register)* ~ ";"}
asm_register = {ident}
asm_immediate = {"i" ~ u64_integer}
asm_immediate = {"i" ~ basic_integer}
opcode = {ident}

// control flow
Expand Down
Loading

0 comments on commit 8fe74e0

Please sign in to comment.