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

WIP: Traits and Generic function parameter #697

Closed
wants to merge 2 commits into from
Closed
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 crates/analyzer/src/db/queries/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ pub fn module_all_items(db: &dyn AnalyzerDb, module: ModuleId) -> Rc<[Item]> {
}))))
}
ast::ModuleStmt::Pragma(_) => None,
ast::ModuleStmt::Trait(_) => None,
ast::ModuleStmt::Use(_) => None,
ast::ModuleStmt::Event(node) => Some(Item::Event(db.intern_event(Rc::new(Event {
ast: node.clone(),
Expand Down
2 changes: 2 additions & 0 deletions crates/lowering/src/mappers/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub fn func_def(context: &mut ModuleContext, function: FunctionId) -> Node<fe::F
unsafe_,
name,
args,
generic_params,
return_type: return_type_node,
body,
} = &node.kind;
Expand Down Expand Up @@ -113,6 +114,7 @@ pub fn func_def(context: &mut ModuleContext, function: FunctionId) -> Node<fe::F
unsafe_: *unsafe_,
name: name.clone(),
args,
generic_params: generic_params.clone(),
return_type: Some(lowered_return_type),
body: lowered_body,
};
Expand Down
1 change: 1 addition & 0 deletions crates/lowering/src/mappers/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ fn list_expr_to_fn_def(array: &Array) -> ast::Function {
unsafe_: None,
name: names::list_expr_generator_fn_name(array).into_node(),
args,
generic_params: Vec::new().into_node(),
return_type,
body: [vec![var_decl], assignments, vec![return_stmt]].concat(),
}
Expand Down
36 changes: 36 additions & 0 deletions crates/parser/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub enum ModuleStmt {
Contract(Node<Contract>),
Constant(Node<ConstantDecl>),
Struct(Node<Struct>),
Trait(Node<Trait>),
Function(Node<Function>),
Event(Node<Event>),
ParseError(Span),
Expand Down Expand Up @@ -87,6 +88,12 @@ pub struct Struct {
pub pub_qual: Option<Span>,
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)]
pub struct Trait {
pub name: Node<SmolStr>,
pub pub_qual: Option<Span>,
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)]
pub enum TypeDesc {
Unit,
Expand Down Expand Up @@ -123,6 +130,24 @@ impl Spanned for GenericArg {
}
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)]
pub enum GenericParameter {
Unbounded(Node<SmolStr>),
Bounded {
name: Node<SmolStr>,
bound: Node<SmolStr>,
},
}

impl Spanned for GenericParameter {
fn span(&self) -> Span {
match self {
GenericParameter::Unbounded(node) => node.span,
GenericParameter::Bounded { name, bound } => name.span + bound.span,
}
}
}

/// struct or contract field, with optional 'pub' and 'const' qualifiers
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)]
pub struct Field {
Expand Down Expand Up @@ -153,6 +178,7 @@ pub struct Function {
pub pub_: Option<Span>,
pub unsafe_: Option<Span>,
pub name: Node<SmolStr>,
pub generic_params: Node<Vec<GenericParameter>>,
pub args: Vec<Node<FunctionArg>>,
pub return_type: Option<Node<TypeDesc>>,
pub body: Vec<Node<FuncStmt>>,
Expand Down Expand Up @@ -406,6 +432,7 @@ impl Spanned for ModuleStmt {
match self {
ModuleStmt::Pragma(inner) => inner.span,
ModuleStmt::Use(inner) => inner.span,
ModuleStmt::Trait(inner) => inner.span,
ModuleStmt::TypeAlias(inner) => inner.span,
ModuleStmt::Contract(inner) => inner.span,
ModuleStmt::Constant(inner) => inner.span,
Expand Down Expand Up @@ -437,6 +464,7 @@ impl fmt::Display for ModuleStmt {
match self {
ModuleStmt::Pragma(node) => write!(f, "{}", node.kind),
ModuleStmt::Use(node) => write!(f, "{}", node.kind),
ModuleStmt::Trait(node) => write!(f, "{}", node.kind),
ModuleStmt::TypeAlias(node) => write!(f, "{}", node.kind),
ModuleStmt::Contract(node) => write!(f, "{}", node.kind),
ModuleStmt::Constant(node) => write!(f, "{}", node.kind),
Expand Down Expand Up @@ -504,6 +532,14 @@ impl fmt::Display for ConstantDecl {
}
}

impl fmt::Display for Trait {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
writeln!(f, "trait {}:", self.name.kind)?;

Ok(())
}
}

impl fmt::Display for TypeAlias {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "type {} = {}", self.name.kind, self.typ.kind)
Expand Down
1 change: 1 addition & 0 deletions crates/parser/src/grammar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ pub mod contracts;
pub mod expressions;
pub mod functions;
pub mod module;
pub mod traits;
pub mod types;
95 changes: 91 additions & 4 deletions crates/parser/src/grammar/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ use super::expressions::{parse_call_args, parse_expr};
use super::types::parse_type_desc;

use crate::ast::{
BinOperator, Expr, FuncStmt, Function, FunctionArg, RegularFunctionArg, VarDeclTarget,
BinOperator, Expr, FuncStmt, Function, FunctionArg, GenericParameter, RegularFunctionArg,
VarDeclTarget,
};
use crate::lexer::TokenKind;
use crate::node::{Node, Span};
use crate::{Label, ParseFailed, ParseResult, Parser};
use crate::{Label, ParseFailed, ParseResult, Parser, TokenKind};

/// Parse a function definition. The optional `pub` qualifier must be parsed by
/// the caller, and passed in. Next token must be `unsafe` or `fn`.
Expand All @@ -28,7 +28,14 @@ pub fn parse_fn_def(par: &mut Parser, mut pub_qual: Option<Span>) -> ParseResult
}
let fn_tok = par.expect(TokenKind::Fn, "failed to parse function definition")?;
let name = par.expect(TokenKind::Name, "failed to parse function definition")?;
let mut span = fn_tok.span + unsafe_qual + pub_qual + name.span;

let generic_params = if par.peek() == Some(TokenKind::Lt) {
parse_generic_params(par)?
} else {
Node::new(vec![], name.span)
};

let mut span = fn_tok.span + unsafe_qual + pub_qual + name.span + generic_params.span;

let args = match par.peek_or_err()? {
TokenKind::ParenOpen => {
Expand Down Expand Up @@ -87,13 +94,93 @@ pub fn parse_fn_def(par: &mut Parser, mut pub_qual: Option<Span>) -> ParseResult
unsafe_: unsafe_qual,
name: name.into(),
args,
generic_params,
return_type,
body,
},
span,
))
}

/// Parse a single generic function parameter (eg. `T:SomeTrait` in `fn foo<T: SomeTrait>(some_arg: u256) -> bool`).
/// # Panics
/// Panics if the first token isn't `Name`.
pub fn parse_generic_param(par: &mut Parser) -> ParseResult<GenericParameter> {
use TokenKind::*;

let name = par.assert(Name);
match par.optional(Colon) {
Some(_) => {
let bound = par.assert(Name);
Ok(GenericParameter::Bounded {
name: Node::new(name.text.into(), name.span),
bound: Node::new(bound.text.into(), bound.span),
})
}
None => Ok(GenericParameter::Unbounded(Node::new(
name.text.into(),
name.span,
))),
}
}

/// Parse an angle-bracket-wrapped list of generic arguments (eg. `<T, R: SomeTrait>` in `fn foo<T, R: SomeTrait>(some_arg: u256) -> bool`).
/// # Panics
/// Panics if the first token isn't `<`.
pub fn parse_generic_params(par: &mut Parser) -> ParseResult<Node<Vec<GenericParameter>>> {
use TokenKind::*;
let mut span = par.assert(Lt).span;

let mut args = vec![];

let expect_end = |par: &mut Parser| {
// If there's no comma, the next token must be `>`
match par.peek_or_err()? {
Gt => Ok(par.next()?.span),
_ => {
let tok = par.next()?;
par.unexpected_token_error(
tok.span,
"Unexpected token while parsing generic arg list",
vec!["Expected a `>` here".to_string()],
);
Err(ParseFailed)
}
}
};

loop {
match par.peek_or_err()? {
Gt => {
span += par.next()?.span;
break;
}
Name => {
let typ = parse_generic_param(par)?;
args.push(typ);
if par.peek() == Some(Comma) {
par.next()?;
} else {
span += expect_end(par)?;
break;
}
}

// Invalid generic argument.
_ => {
let tok = par.next()?;
par.unexpected_token_error(
tok.span,
"failed to parse list of generic function parameters",
vec!["Expected a generic parameter name such as `T` here".to_string()],
);
return Err(ParseFailed);
}
}
}
Ok(Node::new(args, span))
}

fn parse_fn_param_list(par: &mut Parser) -> ParseResult<Node<Vec<Node<FunctionArg>>>> {
let mut span = par.assert(TokenKind::ParenOpen).span;
let mut params = vec![];
Expand Down
2 changes: 2 additions & 0 deletions crates/parser/src/grammar/module.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use super::contracts::parse_contract_def;
use super::expressions::parse_expr;
use super::functions::parse_fn_def;
use super::traits::parse_trait_def;
use super::types::{
parse_event_def, parse_path_tail, parse_struct_def, parse_type_alias, parse_type_desc,
};
Expand Down Expand Up @@ -44,6 +45,7 @@ pub fn parse_module_stmt(par: &mut Parser) -> ParseResult<ModuleStmt> {
TokenKind::Use => ModuleStmt::Use(parse_use(par)?),
TokenKind::Contract => ModuleStmt::Contract(parse_contract_def(par, None)?),
TokenKind::Struct => ModuleStmt::Struct(parse_struct_def(par, None)?),
TokenKind::Trait => ModuleStmt::Trait(parse_trait_def(par, None)?),
TokenKind::Type => ModuleStmt::TypeAlias(parse_type_alias(par, None)?),
TokenKind::Const => ModuleStmt::Constant(parse_constant(par, None)?),

Expand Down
55 changes: 55 additions & 0 deletions crates/parser/src/grammar/traits.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use crate::ast::Trait;
use crate::grammar::functions::parse_single_word_stmt;
use crate::node::{Node, Span};
use crate::{ParseFailed, ParseResult, Parser, TokenKind};

/// Parse a trait definition.
/// # Panics
/// Panics if the next token isn't `trait`.
pub fn parse_trait_def(par: &mut Parser, trait_pub_qual: Option<Span>) -> ParseResult<Node<Trait>> {
let trait_tok = par.assert(TokenKind::Trait);

// trait Event:
// pass
//

let trait_name = par.expect_with_notes(
TokenKind::Name,
"failed to parse trait definition",
|_| vec!["Note: `trait` must be followed by a name, which must start with a letter and contain only letters, numbers, or underscores".into()],
)?;

let header_span = trait_tok.span + trait_name.span;
par.enter_block(header_span, "trait definition")?;

loop {
match par.peek() {
Some(TokenKind::Pass) => {
parse_single_word_stmt(par)?;
}
Some(TokenKind::Dedent) => {
par.next()?;
break;
}
None => break,
Some(_) => {
let tok = par.next()?;
par.unexpected_token_error(
tok.span,
"failed to parse trait definition body",
vec![],
);
return Err(ParseFailed);
}
};
}

let span = header_span + trait_pub_qual;
Ok(Node::new(
Trait {
name: Node::new(trait_name.text.into(), trait_name.span),
pub_qual: trait_pub_qual,
},
span,
))
}
3 changes: 3 additions & 0 deletions crates/parser/src/lexer/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ pub enum TokenKind {
SelfValue,
#[token("struct")]
Struct,
#[token("trait")]
Trait,
#[token("type")]
Type,
#[token("unsafe")]
Expand Down Expand Up @@ -249,6 +251,7 @@ impl TokenKind {
Revert => "keyword `revert`",
SelfValue => "keyword `self`",
Struct => "keyword `struct`",
Trait => "keyword `trait`",
Type => "keyword `type`",
Unsafe => "keyword `unsafe`",
While => "keyword `while`",
Expand Down
2 changes: 2 additions & 0 deletions crates/parser/tests/cases/parse_ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ test_parse! { type_tuple, types::parse_type_desc, "(u8, u16, address, Map<u8, u8
test_parse! { type_unit, types::parse_type_desc, "()" }

test_parse! { fn_def, try_parse_module, "fn transfer(from sender: address, to recip: address, _ val: u64) -> bool:\n false"}

test_parse! { fn_def_generic, try_parse_module, "fn foo<T, R: Event>(this: T, that: R, _ val: u64) -> bool:\n false"}
test_parse! { fn_def_pub, try_parse_module, "pub fn foo21(x: bool, y: address,) -> bool:\n x"}
test_parse! { fn_def_unsafe, try_parse_module, "unsafe fn foo21(x: bool, y: address,) -> bool:\n x"}
test_parse! { fn_def_pub_unsafe, try_parse_module, "pub unsafe fn foo21(x: bool, y: address,) -> bool:\n x"}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ error: failed to parse module
1if x:
^^ unexpected token
= Note: expected import, contract, struct, type, const or event
= Note: expected import, contract, struct, trait, type, const or event


Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
source: crates/parser/tests/cases/parse_ast.rs
expression: "ast_string(stringify!(contract_def), module::parse_module,\n r#\"contract Foo:\n x: address\n pub y: u8\n pub const z: Map<u8, address>\n pub fn foo() -> u8:\n return 10\n event Bar:\n idx from: address\n\"#)"
expression: "ast_string(stringify!(contract_def), try_parse_module,\n r#\"contract Foo:\n x: address\n pub y: u8\n pub const z: Map<u8, address>\n pub fn foo() -> u8:\n return 10\n event Bar:\n idx from: address\n\"#)"

---
Node(
Expand Down Expand Up @@ -145,6 +145,13 @@ Node(
end: 83,
),
),
generic_params: Node(
kind: [],
span: Span(
start: 80,
end: 83,
),
),
args: [],
return_type: Some(Node(
kind: Base(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
source: crates/parser/tests/cases/parse_ast.rs
expression: "ast_string(stringify!(fn_def), try_parse_module,\n \"fn transfer(from sender: address, to recip: address, _ val: u64) -> bool:\\n false\")"
expression: "ast_string(stringify!(fn_def), try_parse_module,\n \"fn transfer(from sender: address, to recip: address, _ val: u64) -> bool:\\n false\")"

---
Node(
Expand All @@ -17,6 +17,13 @@ Node(
end: 11,
),
),
generic_params: Node(
kind: [],
span: Span(
start: 3,
end: 11,
),
),
args: [
Node(
kind: Regular(RegularFunctionArg(
Expand Down
Loading