Skip to content

Commit

Permalink
Support type arguments on methods (FuelLabs#2974)
Browse files Browse the repository at this point in the history
Fixes FuelLabs#2948.

Now it's possible to write `foo.bar::<Baz>()`.
The parser will also do recovery for `foo.field::<Args>`.

Co-authored-by: Emily Herbert <[email protected]>
Co-authored-by: Mohammad Fawaz <[email protected]>
  • Loading branch information
3 people authored Oct 10, 2022
1 parent dab1c74 commit b6b1432
Show file tree
Hide file tree
Showing 20 changed files with 144 additions and 39 deletions.
4 changes: 2 additions & 2 deletions sway-ast/src/expr/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::priv_prelude::*;
use crate::{priv_prelude::*, PathExprSegment};

pub mod asm;
pub mod op_code;
Expand Down Expand Up @@ -50,7 +50,7 @@ pub enum Expr {
MethodCall {
target: Box<Expr>,
dot_token: DotToken,
name: Ident,
path_seg: PathExprSegment,
contract_args_opt: Option<Braces<Punctuated<ExprStructField, CommaToken>>>,
args: Parens<Punctuated<Expr, CommaToken>>,
},
Expand Down
8 changes: 4 additions & 4 deletions sway-ast/src/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ impl Spanned for PathExpr {
None => self.prefix.span(),
};
let end = match self.suffix.last() {
Some((_double_colon_token, path_expr_segment)) => path_expr_segment.span(),
Some((_, path_expr_segment)) => path_expr_segment.span(),
None => self.prefix.span(),
};
Span::join(start, end)
Expand All @@ -51,7 +51,7 @@ impl Spanned for PathExprSegment {
None => self.name.span(),
};
let end = match &self.generics_opt {
Some((_double_colon_token, generic_args)) => generic_args.span(),
Some((_, generic_args)) => generic_args.span(),
None => self.name.span(),
};
Span::join(start, end)
Expand All @@ -75,7 +75,7 @@ impl Spanned for PathType {
None => self.prefix.span(),
};
let end = match self.suffix.last() {
Some((_double_colon_token, path_type_segment)) => path_type_segment.span(),
Some((_, path_type_segment)) => path_type_segment.span(),
None => self.prefix.span(),
};
Span::join(start, end)
Expand All @@ -96,7 +96,7 @@ impl Spanned for PathTypeSegment {
None => self.name.span(),
};
let end = match &self.generics_opt {
Some((_double_colon_token, generic_args)) => generic_args.span(),
Some((_, generic_args)) => generic_args.span(),
None => self.name.span(),
};
Span::join(start, end)
Expand Down
26 changes: 15 additions & 11 deletions sway-core/src/convert_parse_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1265,16 +1265,21 @@ fn struct_path_and_fields_to_struct_expression(
fn method_call_fields_to_method_application_expression(
ec: &mut ErrorContext,
target: Box<Expr>,
name: Ident,
path_seg: PathExprSegment,
contract_args_opt: Option<Braces<Punctuated<ExprStructField, CommaToken>>>,
args: Parens<Punctuated<Expr, CommaToken>>,
) -> Result<Box<MethodApplicationExpression>, ErrorEmitted> {
let (method_name, type_arguments) = path_expr_segment_to_ident_or_type_argument(ec, path_seg)?;

let span = match &*type_arguments {
[] => method_name.span(),
[.., last] => Span::join(method_name.span(), last.span.clone()),
};

let method_name_binding = TypeBinding {
inner: MethodName::FromModule {
method_name: name.clone(),
},
type_arguments: vec![],
span: name.span(),
inner: MethodName::FromModule { method_name },
type_arguments,
span,
};
let contract_call_params = match contract_args_opt {
None => Vec::new(),
Expand Down Expand Up @@ -1690,7 +1695,7 @@ fn expr_to_expression(ec: &mut ErrorContext, expr: Expr) -> Result<Expression, E
},
Expr::MethodCall {
target,
name,
path_seg,
args,
contract_args_opt,
..
Expand All @@ -1699,7 +1704,7 @@ fn expr_to_expression(ec: &mut ErrorContext, expr: Expr) -> Result<Expression, E
method_call_fields_to_method_application_expression(
ec,
target,
name,
path_seg,
contract_args_opt,
args,
)?;
Expand Down Expand Up @@ -2272,9 +2277,8 @@ fn path_expr_segment_to_ident_or_type_argument(
};
return Err(ec.error(error));
}
let generic_args = generics_opt.map(|(_, y)| y);
let type_args = match generic_args {
Some(x) => generic_args_to_type_arguments(ec, x)?,
let type_args = match generics_opt {
Some((_, x)) => generic_args_to_type_arguments(ec, x)?,
None => Default::default(),
};
Ok((name, type_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 @@ -76,6 +76,8 @@ pub enum ParseErrorKind {
ExpectedDocComment,
#[error("Use the `struct` keyword to define records, instead of `class`.")]
UnexpectedClass,
#[error("Field projections, e.g., `foo.bar` cannot have type arguments.")]
FieldProjectionWithGenericArgs,
}

#[derive(Debug, Error, Clone, PartialEq, Eq, Hash)]
Expand Down
37 changes: 24 additions & 13 deletions sway-parse/src/expr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ use sway_ast::punctuated::Punctuated;
use sway_ast::token::Delimiter;
use sway_ast::{
AbiCastArgs, CodeBlockContents, Expr, ExprArrayDescriptor, ExprStructField,
ExprTupleDescriptor, IfCondition, IfExpr, LitInt, Literal, MatchBranch, MatchBranchKind,
Statement, StatementLet,
ExprTupleDescriptor, GenericArgs, IfCondition, IfExpr, LitInt, Literal, MatchBranch,
MatchBranchKind, PathExprSegment, Statement, StatementLet,
};
use sway_types::{Ident, Spanned};
use sway_types::{Ident, Span, Spanned};

mod asm;
pub mod op_code;
Expand Down Expand Up @@ -495,36 +495,36 @@ fn parse_projection(parser: &mut Parser, ctx: ParseExprCtx) -> ParseResult<Expr>
let target = Box::new(expr);

// Try parsing a field access or a method call.
if let Some(name) = parser.take() {
if let Some(path_seg) = parser.guarded_parse::<Ident, PathExprSegment>()? {
if !ctx.parsing_conditional {
if let Some(contract_args) = Braces::try_parse(parser)? {
let contract_args_opt = Some(contract_args);
let args = Parens::parse(parser)?;
expr = Expr::MethodCall {
target,
dot_token,
name,
contract_args_opt,
args,
path_seg,
contract_args_opt: Some(contract_args),
args: Parens::parse(parser)?,
};
continue;
}
}
if let Some(args) = Parens::try_parse(parser)? {
let contract_args_opt = None;
expr = Expr::MethodCall {
target,
dot_token,
name,
contract_args_opt,
path_seg,
contract_args_opt: None,
args,
};
continue;
}

// No arguments, so this is a field projection.
ensure_field_projection_no_generics(parser, &path_seg.generics_opt);
expr = Expr::FieldProjection {
target,
dot_token,
name,
name: path_seg.name,
};
continue;
}
Expand Down Expand Up @@ -569,6 +569,17 @@ fn parse_projection(parser: &mut Parser, ctx: ParseExprCtx) -> ParseResult<Expr>
}
}

/// Ensure we don't have `foo.bar::<...>` where `bar` isn't a method call.
fn ensure_field_projection_no_generics(
parser: &mut Parser,
generic_args: &Option<(DoubleColonToken, GenericArgs)>,
) {
if let Some((dct, generic_args)) = generic_args {
let span = Span::join(dct.span(), generic_args.span());
parser.emit_error_with_span(ParseErrorKind::FieldProjectionWithGenericArgs, span);
}
}

fn parse_func_app(parser: &mut Parser, ctx: ParseExprCtx) -> ParseResult<Expr> {
let mut expr = parse_atom(parser, ctx)?;
if expr.is_control_flow() && ctx.at_start_of_statement {
Expand Down
18 changes: 9 additions & 9 deletions swayfmt/src/utils/language/expr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ use sway_ast::{
keywords::{CommaToken, DotToken},
punctuated::Punctuated,
token::Delimiter,
Braces, CodeBlockContents, Expr, ExprStructField, MatchBranch, PathExpr,
Braces, CodeBlockContents, Expr, ExprStructField, MatchBranch, PathExpr, PathExprSegment,
};
use sway_types::{Ident, Spanned};
use sway_types::Spanned;

pub(crate) mod abi_cast;
pub(crate) mod asm_block;
Expand Down Expand Up @@ -210,7 +210,7 @@ impl Format for Expr {
Self::MethodCall {
target,
dot_token,
name,
path_seg,
contract_args_opt,
args,
} => {
Expand All @@ -227,7 +227,7 @@ impl Format for Expr {
format_method_call(
target,
dot_token,
name,
path_seg,
contract_args_opt,
args,
&mut buf,
Expand All @@ -254,7 +254,7 @@ impl Format for Expr {
format_method_call(
target,
dot_token,
name,
path_seg,
contract_args_opt,
args,
formatted_code,
Expand Down Expand Up @@ -622,7 +622,7 @@ fn format_expr_struct(
fn format_method_call(
target: &Expr,
dot_token: &DotToken,
name: &Ident,
path_seg: &PathExprSegment,
contract_args_opt: &Option<Braces<Punctuated<ExprStructField, CommaToken>>>,
args: &Parens<Punctuated<Expr, CommaToken>>,
formatted_code: &mut FormattedCode,
Expand All @@ -638,7 +638,7 @@ fn format_method_call(
}
target.format(formatted_code, formatter)?;
write!(formatted_code, "{}", dot_token.span().as_str())?;
name.format(formatted_code, formatter)?;
path_seg.format(formatted_code, formatter)?;
if let Some(contract_args) = &contract_args_opt {
ExprStructField::open_curly_brace(formatted_code, formatter)?;
let contract_args = &contract_args.get();
Expand Down Expand Up @@ -787,14 +787,14 @@ fn expr_leaf_spans(expr: &Expr) -> Vec<ByteSpan> {
Expr::MethodCall {
target,
dot_token,
name,
path_seg,
contract_args_opt,
args,
} => {
let mut collected_spans = Vec::new();
collected_spans.append(&mut target.leaf_spans());
collected_spans.push(ByteSpan::from(dot_token.span()));
collected_spans.push(ByteSpan::from(name.span()));
collected_spans.push(ByteSpan::from(path_seg.span()));
if let Some(contract_args) = contract_args_opt {
collected_spans.append(&mut contract_args.leaf_spans());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[[package]]
name = 'method_type_args_sound'
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 = "method_type_args_sound"
entry = "main.sw"
implicit-std = false
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
contract;

struct A {}

impl A {
fn generic<T>(self, x: T) -> T { x }
}

fn foo() -> bool {
A {}.generic::<u8>(true)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
category = "fail"

# check: $()This parameter was declared as type u8, but argument of type bool was provided.
# check: $()Mismatched types.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[[package]]
name = 'unexpected_field_type_args'
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 = "unexpected_field_type_args"
entry = "main.sw"
implicit-std = false
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
contract;

struct A {
field: bool,
}

fn foo(a: A) -> bool { a.field::<bool> && 0 } // recovery witness.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
category = "fail"

# check: $()Field projections, e.g., `foo.bar` cannot have type arguments.
# check: $()Mismatched types.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[[package]]
name = 'core'
source = 'path+from-root-1C4B691D300E54B2'
dependencies = []

[[package]]
name = 'method_type_args'
source = 'root'
dependencies = ['std']

[[package]]
name = 'std'
source = 'path+from-root-1C4B691D300E54B2'
dependencies = ['core']
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[project]
authors = ["Fuel Labs <[email protected]>"]
license = "Apache-2.0"
name = "method_type_args"
entry = "main.sw"

[dependencies]
std = { path = "../../../../../../../sway-lib-std" }
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
script;

struct A {}

impl A {
fn generic<T>(self, x: T) -> T { x }
}

fn foo() -> bool {
A {}.generic::<bool>(true)
}

fn main() {
let _ = foo();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
category = "run"
expected_result = { action = "return", value = 0 }
validate_abi = false

0 comments on commit b6b1432

Please sign in to comment.