diff --git a/sway-core/src/asm_generation/mod.rs b/sway-core/src/asm_generation/mod.rs index 8524cd09f4d..3f79a2bd894 100644 --- a/sway-core/src/asm_generation/mod.rs +++ b/sway-core/src/asm_generation/mod.rs @@ -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), diff --git a/sway-core/src/optimize.rs b/sway-core/src/optimize.rs index d55717e5c8e..7d0399c6fa8 100644 --- a/sway-core/src/optimize.rs +++ b/sway-core/src/optimize.rs @@ -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), @@ -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), diff --git a/sway-core/src/parse_tree/literal.rs b/sway-core/src/parse_tree/literal.rs index 9cf8823024b..bfc0bade18c 100644 --- a/sway-core/src/parse_tree/literal.rs +++ b/sway-core/src/parse_tree/literal.rs @@ -22,6 +22,7 @@ pub enum Literal { U32(u32), U64(u64), String(span::Span), + Numeric(u64), Boolean(bool), Byte(u8), B256([u8; 32]), @@ -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, @@ -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, _) = 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 { @@ -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(), @@ -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(), @@ -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(), @@ -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(), @@ -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, @@ -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>, + ) -> 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( @@ -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>, -) -> 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 }, - ), - } -} diff --git a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs index d195300dab1..55c71cf2b43 100644 --- a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs +++ b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs @@ -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) } @@ -338,6 +362,7 @@ impl TypedExpression { fn type_check_literal(lit: Literal, span: Span) -> CompileResult { 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), @@ -1780,6 +1805,98 @@ impl TypedExpression { } } + fn resolve_numeric_literal( + lit: Literal, + span: Span, + new_type: TypeId, + ) -> CompileResult { + 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!( "{} ({})", diff --git a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression_variant.rs b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression_variant.rs index c923bde475d..e5e96e0a29a 100644 --- a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression_variant.rs +++ b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression_variant.rs @@ -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(), diff --git a/sway-core/src/sway.pest b/sway-core/src/sway.pest index 3afe2e478ef..ea33c846dd9 100644 --- a/sway-core/src/sway.pest +++ b/sway-core/src/sway.pest @@ -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*} @@ -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)} @@ -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 @@ -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 diff --git a/sway-core/src/type_engine/engine.rs b/sway-core/src/type_engine/engine.rs index 54678b3f4ff..d1590cc498b 100644 --- a/sway-core/src/type_engine/engine.rs +++ b/sway-core/src/type_engine/engine.rs @@ -74,13 +74,13 @@ impl Engine { } ( - ref received_info @ UnsignedInteger(recieved_width), + ref received_info @ UnsignedInteger(received_width), ref expected_info @ UnsignedInteger(expected_width), ) => { // E.g., in a variable declaration `let a: u32 = 10u64` the 'expected' type will be // the annotation `u32`, and the 'received' type is 'self' of the initialiser, or // `u64`. So we're casting received TO expected. - let warn = match numeric_cast_compat(expected_width, recieved_width) { + let warn = match numeric_cast_compat(expected_width, received_width) { NumericCastCompatResult::CastableWithWarning(warn) => { vec![CompileWarning { span: span.clone(), @@ -92,7 +92,7 @@ impl Engine { } }; - // Cast the expected type to the recieved type. + // Cast the expected type to the received type. self.slab .replace(received, received_info, expected_info.clone()); Ok(warn) diff --git a/sway-core/src/type_engine/type_info.rs b/sway-core/src/type_engine/type_info.rs index 2fa3731acde..964b28f6c7c 100644 --- a/sway-core/src/type_engine/type_info.rs +++ b/sway-core/src/type_engine/type_info.rs @@ -157,7 +157,7 @@ impl TypeInfo { } Some(array_elem_count_pair) => { match array_elem_count_pair.as_rule() { - Rule::u64_integer => { + Rule::basic_integer => { // Parse the count directly to a usize. check!( array_elem_count_pair diff --git a/sway-server/src/utils/common.rs b/sway-server/src/utils/common.rs index c5bdcf2e79d..e17d7fba7c9 100644 --- a/sway-server/src/utils/common.rs +++ b/sway-server/src/utils/common.rs @@ -23,6 +23,7 @@ pub(crate) fn extract_var_body(var_dec: &VariableDeclaration) -> VarBody { Literal::U16(_) => VarBody::Type("u16".into()), Literal::U32(_) => VarBody::Type("u32".into()), Literal::U64(_) => VarBody::Type("u64".into()), + Literal::Numeric(_) => VarBody::Type("u64".into()), Literal::String(len) => VarBody::Type(format!("str[{}]", len.as_str().len())), Literal::Boolean(_) => VarBody::Type("bool".into()), Literal::Byte(_) => VarBody::Type("u8".into()), diff --git a/test/src/e2e_vm_tests/mod.rs b/test/src/e2e_vm_tests/mod.rs index ad6c351ccb6..52313c8cd92 100644 --- a/test/src/e2e_vm_tests/mod.rs +++ b/test/src/e2e_vm_tests/mod.rs @@ -119,6 +119,7 @@ pub fn run(filter_regex: Option) { "predicate_calls_impure", "script_calls_impure", "contract_pure_calls_impure", + "literal_too_large_for_type", ]; number_of_tests_run += negative_project_names.iter().fold(0, |acc, name| { if filter(name) { diff --git a/test/src/e2e_vm_tests/test_programs/literal_too_large_for_type/Forc.toml b/test/src/e2e_vm_tests/test_programs/literal_too_large_for_type/Forc.toml new file mode 100644 index 00000000000..47495024b25 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/literal_too_large_for_type/Forc.toml @@ -0,0 +1,6 @@ +[project] +author = "Fuel Labs " +license = "Apache-2.0" +name = "literal_too_large_for_type" +entry = "main.sw" + diff --git a/test/src/e2e_vm_tests/test_programs/literal_too_large_for_type/json_abi_oracle.json b/test/src/e2e_vm_tests/test_programs/literal_too_large_for_type/json_abi_oracle.json new file mode 100644 index 00000000000..0637a088a01 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/literal_too_large_for_type/json_abi_oracle.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/literal_too_large_for_type/src/main.sw b/test/src/e2e_vm_tests/test_programs/literal_too_large_for_type/src/main.sw new file mode 100644 index 00000000000..07cb53d9121 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/literal_too_large_for_type/src/main.sw @@ -0,0 +1,5 @@ +script; + +fn main() { + let x:u8 = 256; +}