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 ada51f7
Show file tree
Hide file tree
Showing 22 changed files with 283 additions and 11 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.

Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ pub fn check_if_name_is_invalid(name: &Ident) -> CompileResult<()> {
INVALID_NAMES
.iter()
.find_map(|x| {
if *x == name.as_str() {
if !name.is_raw_ident() && *x == name.as_str() {
Some(err(
vec![],
[CompileError::InvalidVariableName { name: name.clone() }].to_vec(),
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 @@ -15,4 +15,4 @@ num-traits = "0.2.14"
sway-types = { version = "0.16.1", path = "../sway-types" }
thiserror = "1.0"
unicode-xid = "0.2.2"

phf = { version = "0.10.1", features = ["macros"] }
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
14 changes: 12 additions & 2 deletions sway-parse/src/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,20 @@ impl Parse for PathExpr {
}
}

fn parse_ident(parser: &mut Parser) -> ParseResult<Ident> {
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 +208,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
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
17 changes: 17 additions & 0 deletions sway-types/src/ident.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use std::{
pub struct Ident {
name_override_opt: Option<&'static str>,
span: Span,
is_raw_ident: bool,
}

// custom implementation of Hash so that namespacing isn't reliant on the span itself, which will
Expand Down Expand Up @@ -61,25 +62,41 @@ impl Ident {
}
}

pub fn is_raw_ident(&self) -> bool {
self.is_raw_ident
}

pub fn new(span: Span) -> Ident {
let span = span.trim();
Ident {
name_override_opt: None,
span,
is_raw_ident: false,
}
}

pub fn new_with_raw(span: Span, is_raw_ident: bool) -> Ident {
let span = span.trim();
Ident {
name_override_opt: None,
span,
is_raw_ident,
}
}

pub fn new_with_override(name_override: &'static str, span: Span) -> Ident {
Ident {
name_override_opt: Some(name_override),
span,
is_raw_ident: false,
}
}

pub fn new_no_span(name: &'static str) -> Ident {
Ident {
name_override_opt: Some(name),
span: Span::dummy(),
is_raw_ident: false,
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[[package]]
name = 'reserved_identifiers'
source = 'root'
dependencies = []
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[project]
authors = ["Fuel Labs <[email protected]>"]
license = "Apache-2.0"
name = "reserved_identifiers"
entry = "main.sw"
implicit-std = false
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
script;

// this should fail with an `Identifiers cannot be a reserved keyword.` error

fn main() {
let mut script = 0;
let mut contract = 0;
let mut predicate = 0;
let mut library = 0;
let mut dep = 0;
let mut pub = 0;
let mut use = 0;
let mut as = 0;
let mut struct = 0;
let mut enum = 0;
let mut self = 0;
let mut fn = 0;
let mut trait = 0;
let mut impl = 0;
let mut for = 0;
let mut abi = 0;
let mut const = 0;
let mut storage = 0;
let mut str = 0;
let mut asm = 0;
let mut return = 0;
let mut if = 0;
let mut else = 0;
let mut match = 0;
let mut mut = 0;
let mut let = 0;
let mut while = 0;
let mut where = 0;
let mut ref = 0;
let mut deref = 0;
let mut true = 0;
let mut false = 0;
}

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
category = "fail"
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[[package]]
name = 'core'
source = 'path+from-root-D97961F846794409'
dependencies = []

[[package]]
name = 'simple'
source = 'root'
dependencies = ['core']
Loading

0 comments on commit ada51f7

Please sign in to comment.