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

[Feat] Add network.id and self.address opcodes #27917

Merged
merged 10 commits into from
May 1, 2024
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
114 changes: 57 additions & 57 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ members = [
[workspace.dependencies.snarkvm]
#version = "0.16.19"
git = "https://github.com/AleoHQ/snarkVM"
rev = "2cbf34a"
rev = "da3d78a"

[lib]
path = "leo/lib.rs"
Expand Down
2 changes: 1 addition & 1 deletion compiler/ast/src/expressions/literal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use super::*;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Literal {
// todo: deserialize values here
/// An address literal, e.g., `aleo1qnr4dkkvkgfqph0vzc3y6z2eu975wnpz2925ntjccd5cfqxtyu8s7pyjh9`.
/// An address literal, e.g., `aleo1qnr4dkkvkgfqph0vzc3y6z2eu975wnpz2925ntjccd5cfqxtyu8s7pyjh9` or `hello.aleo`.
Address(String, #[serde(with = "leo_span::span_json")] Span, NodeID),
/// A boolean literal, either `true` or `false`.
Boolean(bool, #[serde(with = "leo_span::span_json")] Span, NodeID),
Expand Down
36 changes: 33 additions & 3 deletions compiler/parser/src/parser/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -441,8 +441,8 @@ impl ParserContext<'_> {

// Parses an external function call `credits.aleo/transfer()` or locator `token.aleo/accounts`.
fn parse_external_resource(&mut self, expr: Expression, network_span: Span) -> Result<Expression> {
// Eat an external function call.
self.eat(&Token::Div); // todo: Make `/` a more general token.
// Parse `/`.
self.expect(&Token::Div)?;

// Parse name.
let name = self.expect_identifier()?;
Expand Down Expand Up @@ -507,8 +507,35 @@ impl ParserContext<'_> {
} else if self.eat(&Token::Leo) {
return Err(ParserError::only_aleo_external_calls(expr.span()).into());
} else if self.eat(&Token::Aleo) {
expr = self.parse_external_resource(expr, self.prev_token.span)?;
if self.token.token == Token::Div {
expr = self.parse_external_resource(expr, self.prev_token.span)?;
} else {
// Parse as address literal, e.g. `hello.aleo`.
if !matches!(expr, Expression::Identifier(_)) {
self.emit_err(ParserError::unexpected(expr.to_string(), "an identifier", expr.span()))
}

expr = Expression::Literal(Literal::Address(
format!("{}.aleo", expr),
expr.span(),
self.node_builder.next_id(),
))
}
} else {
// Parse instances of `self.address`.
if let Expression::Identifier(id) = expr {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be more "proper" to do the swap in a later pass. That way the parsed AST accurately reflects the input program. A canonicalization pass would be good for this. At the very least, we should add a comment to move this to canonicalization.

if id.name == sym::SelfLower && self.token.token == Token::Address {
let span = self.expect(&Token::Address)?;
// Convert `self.address` to the current program name. TODO: Move this conversion to canonicalization pass when the new pass is added.
// Note that the unwrap is safe as in order to get to this stage of parsing a program name must have already been parsed.
return Ok(Expression::Literal(Literal::Address(
format!("{}.aleo", self.program_name.unwrap()),
expr.span() + span,
self.node_builder.next_id(),
)));
}
}

// Parse identifier name.
let name = self.expect_identifier()?;

Expand Down Expand Up @@ -769,6 +796,9 @@ impl ParserContext<'_> {
Token::Future => {
Expression::Identifier(Identifier { name: sym::Future, span, id: self.node_builder.next_id() })
}
Token::Network => {
Expression::Identifier(Identifier { name: sym::network, span, id: self.node_builder.next_id() })
}
t if crate::type_::TYPE_TOKENS.contains(&t) => Expression::Identifier(Identifier {
name: t.keyword_to_symbol().unwrap(),
span,
Expand Down
4 changes: 4 additions & 0 deletions compiler/parser/src/tokenizer/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ pub enum Token {
Block,
Eof,
Leo,
Network,
d0cd marked this conversation as resolved.
Show resolved Hide resolved
}

/// Represents all valid Leo keyword tokens.
Expand Down Expand Up @@ -180,6 +181,7 @@ pub const KEYWORD_TOKENS: &[Token] = &[
Token::Inline,
Token::Let,
Token::Mapping,
Token::Network,
Token::Private,
Token::Program,
Token::Public,
Expand Down Expand Up @@ -237,6 +239,7 @@ impl Token {
Token::Let => sym::Let,
Token::Leo => sym::leo,
Token::Mapping => sym::mapping,
Token::Network => sym::network,
Token::Private => sym::private,
Token::Program => sym::program,
Token::Public => sym::public,
Expand Down Expand Up @@ -374,6 +377,7 @@ impl fmt::Display for Token {
Block => write!(f, "block"),
Leo => write!(f, "leo"),
Eof => write!(f, "<eof>"),
Network => write!(f, "network"),
}
}
}
Expand Down
1 change: 1 addition & 0 deletions compiler/passes/src/code_generation/visit_program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ impl<'a> CodeGenerator<'a> {
// TODO: Figure out a better way to initialize.
self.variable_mapping.insert(&sym::SelfLower, "self".to_string());
self.variable_mapping.insert(&sym::block, "block".to_string());
self.variable_mapping.insert(&sym::network, "network".to_string());
self.current_function = Some(function);

// Construct the header of the function.
Expand Down
4 changes: 3 additions & 1 deletion compiler/passes/src/flattening/flatten_statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,9 @@ impl StatementReconstructor for Flattener<'_> {
let guard = self.construct_guard();

match input.expression {
Expression::Unit(_) | Expression::Identifier(_) => self.returns.push((guard, input)),
Expression::Unit(_) | Expression::Identifier(_) | Expression::Access(_) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are accesses required to be added here? Shouldn't SSA create unique assignment statements for access expressions?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added it because there was a compiler panic when a return statement contained an access expression. Looks like it might not be processed in SSA to ensure proper CG. https://github.com/AleoHQ/leo/blob/e6ba5069dc115837b1df361099db3db265f75676/compiler/passes/src/static_single_assignment/rename_statement.rs#L359

self.returns.push((guard, input))
}
_ => unreachable!("SSA guarantees that the expression is always an identifier or unit expression."),
};

Expand Down
32 changes: 14 additions & 18 deletions compiler/passes/src/type_checking/check_expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,22 +202,12 @@ impl<'a> ExpressionVisitor<'a> for TypeChecker<'a> {
Expression::Identifier(identifier) if identifier.name == sym::SelfLower => match access.name.name {
sym::caller => {
// Check that the operation is not invoked in a `finalize` block.
if self.scope_state.variant == Some(Variant::AsyncFunction) {
self.handler.emit_err(TypeCheckerError::invalid_operation_inside_finalize(
"self.caller",
access.name.span(),
))
}
self.check_access_allowed("self.caller", false, access.name.span());
return Some(Type::Address);
}
sym::signer => {
// Check that operation is not invoked in a `finalize` block.
if self.scope_state.variant == Some(Variant::AsyncFunction) {
self.handler.emit_err(TypeCheckerError::invalid_operation_inside_finalize(
"self.signer",
access.name.span(),
))
}
self.check_access_allowed("self.signer", false, access.name.span());
return Some(Type::Address);
}
_ => {
Expand All @@ -228,18 +218,24 @@ impl<'a> ExpressionVisitor<'a> for TypeChecker<'a> {
Expression::Identifier(identifier) if identifier.name == sym::block => match access.name.name {
sym::height => {
// Check that the operation is invoked in a `finalize` block.
if self.scope_state.variant != Some(Variant::AsyncFunction) {
self.handler.emit_err(TypeCheckerError::invalid_operation_outside_finalize(
"block.height",
access.name.span(),
))
}
self.check_access_allowed("block.height", true, access.name.span());
return Some(Type::Integer(IntegerType::U32));
}
_ => {
self.emit_err(TypeCheckerError::invalid_block_access(access.name.span()));
}
},
// If the access expression is of the form `network.<name>`, then check that the <name> is valid.
Expression::Identifier(identifier) if identifier.name == sym::network => match access.name.name {
sym::id => {
// Check that the operation is not invoked outside a `finalize` block.
self.check_access_allowed("network.id", true, access.name.span());
return Some(Type::Integer(IntegerType::U16));
}
_ => {
self.emit_err(TypeCheckerError::invalid_block_access(access.name.span()));
}
},
_ => {
// Check that the type of `inner` in `inner.name` is a struct.
match self.visit_expression(&access.inner, &None) {
Expand Down
43 changes: 16 additions & 27 deletions compiler/passes/src/type_checking/checker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ impl<'a> TypeChecker<'a> {
/// Emits an error if the correct number of arguments are not provided.
/// Emits an error if the arguments are not of the correct type.
pub(crate) fn check_core_function_call(
&self,
&mut self,
core_function: CoreFunction,
arguments: &[(Option<Type>, Span)],
function_span: Span,
Expand Down Expand Up @@ -978,10 +978,7 @@ impl<'a> TypeChecker<'a> {
}
CoreFunction::MappingGet => {
// Check that the operation is invoked in a `finalize` block.
if self.scope_state.variant != Some(Variant::AsyncFunction) {
self.handler
.emit_err(TypeCheckerError::invalid_operation_outside_finalize("Mapping::get", function_span))
}
self.check_access_allowed("Mapping::get", true, function_span);
// Check that the first argument is a mapping.
if let Some(mapping_type) = self.assert_mapping_type(&arguments[0].0, arguments[0].1) {
// Check that the second argument matches the key type of the mapping.
Expand All @@ -994,12 +991,7 @@ impl<'a> TypeChecker<'a> {
}
CoreFunction::MappingGetOrUse => {
// Check that the operation is invoked in a `finalize` block.
if self.scope_state.variant != Some(Variant::AsyncFunction) {
self.handler.emit_err(TypeCheckerError::invalid_operation_outside_finalize(
"Mapping::get_or",
function_span,
))
}
self.check_access_allowed("Mapping::get_or", true, function_span);
// Check that the first argument is a mapping.
if let Some(mapping_type) = self.assert_mapping_type(&arguments[0].0, arguments[0].1) {
// Check that the second argument matches the key type of the mapping.
Expand All @@ -1014,10 +1006,7 @@ impl<'a> TypeChecker<'a> {
}
CoreFunction::MappingSet => {
// Check that the operation is invoked in a `finalize` block.
if self.scope_state.variant != Some(Variant::AsyncFunction) {
self.handler
.emit_err(TypeCheckerError::invalid_operation_outside_finalize("Mapping::set", function_span))
}
self.check_access_allowed("Mapping::set", true, function_span);
// Check that the first argument is a mapping.
if let Some(mapping_type) = self.assert_mapping_type(&arguments[0].0, arguments[0].1) {
// Cannot modify external mappings.
Expand All @@ -1036,12 +1025,7 @@ impl<'a> TypeChecker<'a> {
}
CoreFunction::MappingRemove => {
// Check that the operation is invoked in a `finalize` block.
if self.scope_state.variant != Some(Variant::AsyncFunction) {
self.handler.emit_err(TypeCheckerError::invalid_operation_outside_finalize(
"Mapping::remove",
function_span,
))
}
self.check_access_allowed("Mapping::remove", true, function_span);
// Check that the first argument is a mapping.
if let Some(mapping_type) = self.assert_mapping_type(&arguments[0].0, arguments[0].1) {
// Cannot modify external mappings.
Expand All @@ -1059,12 +1043,7 @@ impl<'a> TypeChecker<'a> {
}
CoreFunction::MappingContains => {
// Check that the operation is invoked in a `finalize` block.
if self.scope_state.variant != Some(Variant::AsyncFunction) {
self.handler.emit_err(TypeCheckerError::invalid_operation_outside_finalize(
"Mapping::contains",
function_span,
))
}
self.check_access_allowed("Mapping::contains", true, function_span);
// Check that the first argument is a mapping.
if let Some(mapping_type) = self.assert_mapping_type(&arguments[0].0, arguments[0].1) {
// Check that the second argument matches the key type of the mapping.
Expand Down Expand Up @@ -1506,6 +1485,16 @@ impl<'a> TypeChecker<'a> {
self.handler.emit_err(err);
}
}

// Checks if the access operation is valid inside the current function variant.
pub(crate) fn check_access_allowed(&mut self, name: &str, finalize_op: bool, span: Span) {
// Check that the function context matches.
if self.scope_state.variant == Some(Variant::AsyncFunction) && !finalize_op {
self.handler.emit_err(TypeCheckerError::invalid_operation_inside_finalize(name, span))
} else if self.scope_state.variant != Some(Variant::AsyncFunction) && finalize_op {
self.handler.emit_err(TypeCheckerError::invalid_operation_outside_finalize(name, span))
}
}
}

fn types_to_string(types: &[Type]) -> String {
Expand Down
2 changes: 2 additions & 0 deletions compiler/span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,8 @@ symbols! {
stub,
block,
height,
network,
id,
}

/// An interned string.
Expand Down
18 changes: 18 additions & 0 deletions tests/expectations/compiler/address/special_address.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
namespace: Compile
expectation: Pass
outputs:
- - compile:
- initial_symbol_table: f159adcd5ea24c580a6b8535b217667a40173809e5802a0b03db3dc5b9bec9aa
type_checked_symbol_table: 9fd0ee7288d5151305f4cd3820594bd9db7accb589bc936e186709af1df6de21
unrolled_symbol_table: 9fd0ee7288d5151305f4cd3820594bd9db7accb589bc936e186709af1df6de21
initial_ast: 21db64864f84959ad71dace62a2487285c3b6bb74818536b83a54b63a2bd8d82
unrolled_ast: 21db64864f84959ad71dace62a2487285c3b6bb74818536b83a54b63a2bd8d82
ssa_ast: d4e2a516deaa30f8663bb3cd1501c52e3cc781b330dbb149ee3bad0692a8cb59
flattened_ast: 175fbd23f91421e3d47440c8a7e00fb9e3a2bef1147e061cd8a3f2bd0c978098
destructured_ast: a23caa23b3ac10d6c2a1b119af502a9ec4380cf521eb65da2c9e2a5f19d44172
inlined_ast: a23caa23b3ac10d6c2a1b119af502a9ec4380cf521eb65da2c9e2a5f19d44172
dce_ast: a23caa23b3ac10d6c2a1b119af502a9ec4380cf521eb65da2c9e2a5f19d44172
bytecode: d9e6c28f9e5527abe9cdb07b9d35375901446415f5d645b0363597200ee45be7
errors: ""
warnings: ""
18 changes: 18 additions & 0 deletions tests/expectations/compiler/expression/network_id.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
namespace: Compile
expectation: Pass
outputs:
- - compile:
- initial_symbol_table: 02b83350abcecb36109bf268cf52b9fc867ab1893c49badf31ff527156528943
type_checked_symbol_table: 1ace971bd20adb9ce07f802070f05c51733af791ef32c7b1130d4a4b2182768d
unrolled_symbol_table: 1ace971bd20adb9ce07f802070f05c51733af791ef32c7b1130d4a4b2182768d
initial_ast: 6dc0a710ab752f571f4dae9fdb172a7fa1c43e3af3858cbc8cf96c5d510a0c3a
unrolled_ast: 6dc0a710ab752f571f4dae9fdb172a7fa1c43e3af3858cbc8cf96c5d510a0c3a
ssa_ast: e09d30595377e81788433b702f76f1338ff4bb720f8564e2560e5f78ebd18bc0
flattened_ast: 16df732ae63243e249201817b30ae02b8a190072d39894648607970eb2b09192
destructured_ast: b2f615fbb0825b50c961b4014e2e2d60117b543cab0d2e1acd4f3237c878e95e
inlined_ast: d3f7291df3faf6f8a4893b91133fe183d44c35f3d9c4b9d270d71f943482f965
dce_ast: d3f7291df3faf6f8a4893b91133fe183d44c35f3d9c4b9d270d71f943482f965
bytecode: ae04a04e7ffb01dfdd0ae0249f31649302bc120ea928c5ace16bc0879140e1f9
errors: ""
warnings: ""
Loading
Loading