Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Defer integer literal type resolution until after type checking #711

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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(
sezna marked this conversation as resolved.
Show resolved Hide resolved
"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