Skip to content

Commit

Permalink
Add support for raw identifiers and improve reserved keywords checking.
Browse files Browse the repository at this point in the history
This commit deals with the usage and checking of reserved keywords
as identifiers, for code like:

```
fn main() {
    let mut mut = 0;
}

It introduces a new error that checks if an identifier is a reserved
keyword:

```
error
 --> /main.sw:4:13
  |
2 |
3 | fn main() {
4 |     let mut mut = 0;
  |             ^^^ Identifiers cannot be a reserved keyword.
5 | }
  |
____
```

There was an existing issue in the standard library, which
has a library/module named `storage`.

Instead of working around this by renaming it to something else,
an alternative solution with raw identifiers is implemented.

This raw identifier feature is implemented at the lexer level,
and allows you to use keywords as identifiers in places that
generally wouldn't be allowed.

Rust and a bunch of other modern languages also provide this escape
hatch, and it seemed the simplest solution for me to handle the issue.

It activates by declaring an identifier prefixed with `r#`, just like
Rust.

The complexity on the codebase to support this feature is pretty
minimal, but if there any objections to this, I can easily remove it,
but some other solution to the issue above will need to be figured out.

Closes FuelLabs#1996.
  • Loading branch information
tritao committed Jun 23, 2022
1 parent 91b8eee commit 6309ef4
Show file tree
Hide file tree
Showing 31 changed files with 314 additions and 42 deletions.
51 changes: 51 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion examples/identity/src/abi.sw
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
library abi;
library r#abi;

use std::identity::Identity;

Expand Down
4 changes: 2 additions & 2 deletions examples/identity/src/main.sw
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
contract;

dep abi;
dep r#abi;
dep errors;

use abi::IdentityExample;
use r#abi::IdentityExample;
use errors::MyError;

use std::{
Expand Down
3 changes: 0 additions & 3 deletions sway-core/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@ pub const LANGUAGE_NAME: &str = "Sway";
/// The size, in bytes, of a single word in the FuelVM.
pub const VM_WORD_SIZE: u64 = 8;

// Keywords
pub const INVALID_NAMES: &[&str] = &["storage"];

pub const CONTRACT_CALL_GAS_PARAMETER_NAME: &str = "gas";

pub const CONTRACT_CALL_COINS_PARAMETER_NAME: &str = "coins";
Expand Down
1 change: 1 addition & 0 deletions sway-core/src/parse_tree/expression/scrutinee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ pub enum Scrutinee {
}

#[derive(Debug, Clone)]
#[allow(clippy::large_enum_variant)]
pub enum StructScrutineeField {
Rest {
span: Span,
Expand Down
20 changes: 1 addition & 19 deletions sway-core/src/semantic_analysis/ast_node/declaration/variable.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{constants::*, error::*, semantic_analysis::*, type_engine::*, Ident, Visibility};
use crate::{semantic_analysis::*, type_engine::*, Ident, Visibility};

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum VariableMutability {
Expand Down Expand Up @@ -75,21 +75,3 @@ impl CopyTypes for TypedVariableDeclaration {
self.body.copy_types(type_mapping)
}
}

// there are probably more names we should check here, this is the only one that will result in an
// actual issue right now, though
pub fn check_if_name_is_invalid(name: &Ident) -> CompileResult<()> {
INVALID_NAMES
.iter()
.find_map(|x| {
if *x == name.as_str() {
Some(err(
vec![],
[CompileError::InvalidVariableName { name: name.clone() }].to_vec(),
))
} else {
None
}
})
.unwrap_or_else(|| ok((), vec![], vec![]))
}
1 change: 0 additions & 1 deletion sway-core/src/semantic_analysis/ast_node/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,6 @@ impl TypedAstNode {
body,
is_mutable,
}) => {
check_if_name_is_invalid(&name).ok(&mut warnings, &mut errors);
let type_ascription_span = match type_ascription_span {
Some(type_ascription_span) => type_ascription_span,
None => name.span(),
Expand Down
2 changes: 1 addition & 1 deletion sway-lib-std/src/lib.sw
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ dep constants;
dep contract_id;
dep context;
dep hash;
dep storage;
dep r#storage;
dep b512;
dep address;
dep identity;
Expand Down
2 changes: 1 addition & 1 deletion sway-lib-std/src/storage.sw
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
library storage;
library r#storage;

use ::hash::sha256;
use ::context::registers::stack_ptr;
Expand Down
2 changes: 1 addition & 1 deletion sway-parse/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ description = "Sway's parser"
extension-trait = "1.0.1"
num-bigint = "0.4.3"
num-traits = "0.2.14"
phf = { version = "0.10.1", features = ["macros"] }
sway-types = { version = "0.16.1", path = "../sway-types" }
thiserror = "1.0"
unicode-xid = "0.2.2"

7 changes: 6 additions & 1 deletion sway-parse/src/attribute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,12 @@ impl Spanned for Attribute {

impl Parse for Attribute {
fn parse(parser: &mut Parser) -> ParseResult<Self> {
let name = parser.parse()?;
let storage = parser.take::<StorageToken>();
let name = if let Some(storage) = storage {
Ident::from(storage)
} else {
parser.parse()?
};
let args = Parens::try_parse(parser)?;
Ok(Attribute { name, args })
}
Expand Down
2 changes: 2 additions & 0 deletions sway-parse/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ pub enum ParseErrorKind {
InvalidDoubleUnderscore,
#[error("Unexpected rest token, must be at the end of pattern.")]
UnexpectedRestPattern,
#[error("Identifiers cannot be a reserved keyword.")]
ReservedKeywordIdentifier,
}

#[derive(Debug, Error, Clone, PartialEq, Hash)]
Expand Down
4 changes: 2 additions & 2 deletions sway-parse/src/expr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1056,14 +1056,14 @@ fn parse_atom(parser: &mut Parser, ctx: ParseExprCtx) -> ParseResult<Expr> {
);
}
if parser.peek::<TrueToken>().is_some() {
let ident = parser.parse::<Ident>()?;
let ident = parser.parse::<TrueToken>()?;
return Ok(Expr::Literal(Literal::Bool(LitBool {
span: ident.span(),
kind: LitBoolType::True,
})));
}
if parser.peek::<FalseToken>().is_some() {
let ident = parser.parse::<Ident>()?;
let ident = parser.parse::<FalseToken>()?;
return Ok(Expr::Literal(Literal::Bool(LitBool {
span: ident.span(),
kind: LitBoolType::False,
Expand Down
48 changes: 48 additions & 0 deletions sway-parse/src/keywords.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,18 @@ macro_rules! define_keyword (
}
}

impl $ty_name {
pub fn ident(&self) -> Ident {
Ident::new(self.span())
}
}

impl From<$ty_name> for Ident {
fn from(o: $ty_name) -> Ident {
o.ident()
}
}

impl Peek for $ty_name {
fn peek(peeker: Peeker<'_>) -> Option<$ty_name> {
let ident = peeker.peek_ident().ok()?;
Expand Down Expand Up @@ -119,6 +131,42 @@ macro_rules! define_token (
};
);

// Keep this in sync with the list above defined by define_keyword!
pub(crate) const RESERVED_KEYWORDS: phf::Set<&'static str> = phf::phf_set! {
"script",
"contract",
"predicate",
"library",
"dep",
"pub",
"use",
"as",
"struct",
"enum",
"self",
"fn",
"trait",
"impl",
"for",
"abi",
"const",
"storage",
"str",
"asm",
"return",
"if",
"else",
"match",
"mut",
"let",
"while",
"where",
"ref",
"deref",
"true",
"false",
};

define_token!(SemicolonToken, "a semicolon", [Semicolon], []);
define_token!(
ForwardSlashToken,
Expand Down
7 changes: 7 additions & 0 deletions sway-parse/src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,13 @@ impl Parse for Ident {
));
}

if !ident.is_raw_ident() && RESERVED_KEYWORDS.contains(ident_str) {
return Err(parser.emit_error_with_span(
ParseErrorKind::ReservedKeywordIdentifier,
ident.span(),
));
}

Ok(ident)
}
None => Err(parser.emit_error(ParseErrorKind::ExpectedIdent)),
Expand Down
18 changes: 16 additions & 2 deletions sway-parse/src/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,24 @@ impl Parse for PathExpr {
}
}

fn parse_ident(parser: &mut Parser) -> ParseResult<Ident> {
if parser.peek::<StorageToken>().is_some() {
let token = parser.parse::<StorageToken>()?;
let ident: Ident = Ident::from(token);
Ok(ident)
} else if parser.peek::<SelfToken>().is_some() {
let token = parser.parse::<SelfToken>()?;
let ident: Ident = Ident::from(token);
Ok(ident)
} else {
parser.parse::<Ident>()
}
}

impl Parse for PathExprSegment {
fn parse(parser: &mut Parser) -> ParseResult<PathExprSegment> {
let fully_qualified = parser.take();
let name = parser.parse()?;
let name = parse_ident(parser)?;
let generics_opt = if parser
.peek2::<DoubleColonToken, OpenAngleBracketToken>()
.is_some()
Expand Down Expand Up @@ -198,7 +212,7 @@ impl Parse for PathType {
impl Parse for PathTypeSegment {
fn parse(parser: &mut Parser) -> ParseResult<PathTypeSegment> {
let fully_qualified = parser.take();
let name = parser.parse()?;
let name = parse_ident(parser)?;
let generics_opt = if parser.peek::<OpenAngleBracketToken>().is_some() {
let generics = parser.parse()?;
Some((None, generics))
Expand Down
4 changes: 2 additions & 2 deletions sway-parse/src/pattern.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,14 @@ impl Parse for Pattern {
return Ok(Pattern::Var { mutable, name });
}
if parser.peek::<TrueToken>().is_some() {
let ident = parser.parse::<Ident>()?;
let ident = parser.parse::<TrueToken>()?;
return Ok(Pattern::Literal(Literal::Bool(LitBool {
span: ident.span(),
kind: LitBoolType::True,
})));
}
if parser.peek::<FalseToken>().is_some() {
let ident = parser.parse::<Ident>()?;
let ident = parser.parse::<FalseToken>()?;
return Ok(Pattern::Literal(Literal::Bool(LitBool {
span: ident.span(),
kind: LitBoolType::False,
Expand Down
14 changes: 12 additions & 2 deletions sway-parse/src/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ pub fn lex(
.peekable();
let mut parent_token_trees = Vec::new();
let mut token_trees = Vec::new();
while let Some((index, character)) = char_indices.next() {
while let Some((mut index, mut character)) = char_indices.next() {
if character.is_whitespace() {
continue;
}
Expand Down Expand Up @@ -395,6 +395,16 @@ pub fn lex(
continue;
}
if character.is_xid_start() || character == '_' {
let is_raw_ident = character == 'r' && matches!(char_indices.peek(), Some((_, '#')));
if is_raw_ident {
char_indices.next();
if let Some((_, next_character)) = char_indices.peek() {
character = *next_character;
if let Some((next_index, _)) = char_indices.next() {
index = next_index;
}
}
}
let is_single_underscore = character == '_'
&& match char_indices.peek() {
Some((_, next_character)) => !next_character.is_xid_continue(),
Expand All @@ -408,7 +418,7 @@ pub fn lex(
let _ = char_indices.next();
}
let span = span_until(src, index, &mut char_indices, &path);
let ident = Ident::new(span);
let ident = Ident::new_with_raw(span, is_raw_ident);
token_trees.push(TokenTree::Ident(ident));
continue;
}
Expand Down
Loading

0 comments on commit 6309ef4

Please sign in to comment.