Skip to content

Commit

Permalink
feat(compiler): variadic arguments (#3643)
Browse files Browse the repository at this point in the history
This PR adds the possibility of using variadic arguments

Unfortunately, I had to add the `Clone` attribute to many `enum` and `struct` to be able to create a list of arguments compatible with the requirements for verifying the typing of the variadic argument.

I needed to implement `PartialEq` for `Type`, `TypeRef`, `Class`, `Interface`, `Struct`, and `Enum`.
I'm not sure if it was done in the best way, but it fulfills this specific case.

- [x] Validates if the last argument (variadic) of the function accepts multiple elements.
- [x] Validates if the only argument (variadic) of the function accepts multiple elements.
- [x] Validates if all types of the variadic argument are the same.
- [ ] Accept range as a variadic argument.

Closes #125 
Closes #397

## Checklist

- [x] Title matches [Winglang's style guide](https://www.winglang.io/contributing/start-here/pull_requests#how-are-pull-request-titles-formatted)
- [x] Description explains motivation and solution
- [x] Tests added (always)
- [ ] Docs updated (only required for features)
- [ ] Added `pr/e2e-full` label if this feature requires end-to-end testing

*By submitting this pull request, I confirm that my contribution is made under the terms of the [Wing Cloud Contribution License](https://github.com/winglang/wing/blob/main/CONTRIBUTION_LICENSE.md)*.
  • Loading branch information
marciocadev authored Aug 2, 2023
1 parent 50669c5 commit d5e8bd8
Show file tree
Hide file tree
Showing 14 changed files with 438 additions and 13 deletions.
25 changes: 25 additions & 0 deletions examples/tests/invalid/function_variadic_definition.w
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
let f1 = (...args: Array<num>, x:num) => {};
// ^^^^ Variadic parameters must always be the last parameter in a function.

let f2 = (...nums: Array<num>, ...strs: Array<str>) => {};
// ^^^^ Variadic parameters must always be the last parameter in a function.

let f3 = (...args: Array<num>) => {};
f3(1, true, 2);
// ^^^^ Expected type to be num, but got bool instead.

let f4 = (...args: Set<num>) => {};
// ^^^^ Variadic parameters must be type Array or MutArray.

let f5 = (...args: Array<num>) => {};
f5(args: 1, 2);
// ^^^^^^^^^^^^^^ No named arguments expected

bring cloud;
let bucket1 = new cloud.Bucket() as "bucket1";
let bucket2 = new cloud.Bucket() as "bucket2";
let funcBucket = (...buckets: Array<cloud.Bucket>) => {
assert(buckets.length == 2);
};
funcBucket(bucket1, true, bucket2);
// ^^^^ Expected type to be Bucket, but got bool instead.
30 changes: 30 additions & 0 deletions examples/tests/valid/function_variadic_arguments.w
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
bring cloud;
let bucket1 = new cloud.Bucket() as "bucket1";
let bucket2 = new cloud.Bucket() as "bucket2";
let funcBucket = (...buckets: Array<cloud.Bucket>) => {
assert(buckets.length == 2);
};
funcBucket(bucket1, bucket2);

let func1 = (var x: num, y: str?, var ...args: MutArray<num>) => {
assert(x == 1);
assert(y == "something" || y == nil);
assert(args.length == 4);
for i in args {
assert(i > 0 && i < 5);
}
args.push(10);
assert(args.at(4) == 10);
};
func1(1, "something", 1, 2, 3, 4);
func1(1, nil, 1, 2, 3, 4);

let addNums = (...nums: MutArray<num>):num => {
let var total = 0;
for n in nums {
total = total + n;
}
return total;
};
assert(addNums(1, 2, 3) == 6);
assert(addNums() == 0);
3 changes: 3 additions & 0 deletions libs/tree-sitter-wing/grammar.js
Original file line number Diff line number Diff line change
Expand Up @@ -511,9 +511,12 @@ module.exports = grammar({

access_modifier: ($) => choice("public", "private", "protected"),

variadic: ($) => "...",

parameter_definition: ($) =>
seq(
optional(field("reassignable", $.reassignable)),
optional(field("variadic", $.variadic)),
field("name", $.identifier),
$._type_annotation
),
Expand Down
51 changes: 51 additions & 0 deletions libs/tree-sitter-wing/test/corpus/statements/statements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,57 @@ inflight (callback: (num,num):bool) => {};
return_type: (builtin_type))))
block: (block))))

================================================================================
Inflight closure with variadic argument
================================================================================

inflight (var ...z: MutArray<num>):bool => {};

--------------------------------------------------------------------------------

(source
(expression_statement
(inflight_closure
(inflight_specifier)
parameter_list: (parameter_list
(parameter_definition
reassignable: (reassignable)
variadic: (variadic)
name: (identifier)
type: (mutable_container_type
type_parameter: (builtin_type))))
type: (builtin_type)
block: (block))))

================================================================================
Inflight closure with parameter and variadic argument
================================================================================

inflight (var x: num, y: Array<num>, ...z: Array<num>):bool => {};

--------------------------------------------------------------------------------

(source
(expression_statement
(inflight_closure
(inflight_specifier)
parameter_list: (parameter_list
(parameter_definition
reassignable: (reassignable)
name: (identifier)
type: (builtin_type))
(parameter_definition
name: (identifier)
type: (immutable_container_type
type_parameter: (builtin_type)))
(parameter_definition
variadic: (variadic)
name: (identifier)
type: (immutable_container_type
type_parameter: (builtin_type))))
type: (builtin_type)
block: (block))))

================================================================================
Struct definition
================================================================================
Expand Down
1 change: 1 addition & 0 deletions libs/wingc/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ pub struct FunctionParameter {
pub name: Symbol,
pub type_annotation: TypeAnnotation,
pub reassignable: bool,
pub variadic: bool,
}

#[derive(Debug)]
Expand Down
1 change: 1 addition & 0 deletions libs/wingc/src/fold.rs
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,7 @@ where
name: f.fold_symbol(node.name),
type_annotation: f.fold_type_annotation(node.type_annotation),
reassignable: node.reassignable,
variadic: node.variadic,
}
}

Expand Down
6 changes: 5 additions & 1 deletion libs/wingc/src/jsify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -831,7 +831,11 @@ impl<'a> JSifier<'a> {
let mut parameter_list = vec![];

for p in &func_def.signature.parameters {
parameter_list.push(p.name.to_string());
if p.variadic {
parameter_list.push("...".to_string() + &p.name.to_string());
} else {
parameter_list.push(p.name.to_string());
}
}

let (name, arrow) = match &func_def.name {
Expand Down
4 changes: 4 additions & 0 deletions libs/wingc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ pub fn type_check(
name: "message".into(),
typeref: types.string(),
docs: Docs::with_summary("The message to log"),
variadic: false,
}],
return_type: types.void(),
phase: Phase::Independent,
Expand All @@ -232,6 +233,7 @@ pub fn type_check(
name: "condition".into(),
typeref: types.bool(),
docs: Docs::with_summary("The condition to assert"),
variadic: false,
}],
return_type: types.void(),
phase: Phase::Independent,
Expand All @@ -251,6 +253,7 @@ pub fn type_check(
typeref: types.string(),
name: "message".into(),
docs: Docs::with_summary("The message to throw"),
variadic: false,
}],
return_type: types.void(),
phase: Phase::Independent,
Expand All @@ -268,6 +271,7 @@ pub fn type_check(
typeref: types.string(),
name: "message".into(),
docs: Docs::with_summary("The message to panic with"),
variadic: false,
}],
return_type: types.void(),
phase: Phase::Independent,
Expand Down
13 changes: 7 additions & 6 deletions libs/wingc/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1085,16 +1085,16 @@ impl<'s> Parser<'s> {
fn build_parameter_list(&self, parameter_list_node: &Node, phase: Phase) -> DiagnosticResult<Vec<FunctionParameter>> {
let mut res = vec![];
let mut cursor = parameter_list_node.walk();
for parameter_definition_node in parameter_list_node.named_children(&mut cursor) {
if parameter_definition_node.is_extra() {
for definition_node in parameter_list_node.named_children(&mut cursor) {
if definition_node.is_extra() {
continue;
}

res.push(FunctionParameter {
name: self.check_reserved_symbol(&parameter_definition_node.child_by_field_name("name").unwrap())?,
type_annotation: self
.build_type_annotation(&parameter_definition_node.child_by_field_name("type").unwrap(), phase)?,
reassignable: parameter_definition_node.child_by_field_name("reassignable").is_some(),
name: self.check_reserved_symbol(&definition_node.child_by_field_name("name").unwrap())?,
type_annotation: self.build_type_annotation(&definition_node.child_by_field_name("type").unwrap(), phase)?,
reassignable: definition_node.child_by_field_name("reassignable").is_some(),
variadic: definition_node.child_by_field_name("variadic").is_some(),
});
}

Expand Down Expand Up @@ -1195,6 +1195,7 @@ impl<'s> Parser<'s> {
name: "".into(),
type_annotation: t,
reassignable: false,
variadic: false,
})
}

Expand Down
82 changes: 76 additions & 6 deletions libs/wingc/src/type_check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,7 @@ pub struct FunctionParameter {
pub name: String,
pub typeref: TypeRef,
pub docs: Docs,
pub variadic: bool,
}

#[derive(Clone, Debug)]
Expand Down Expand Up @@ -718,7 +719,7 @@ impl FunctionSignature {
.iter()
.rev()
// TODO - as a hack we treat `anything` arguments like optionals so that () => {} can be a subtype of (any) => {}
.take_while(|arg| arg.typeref.is_option() || arg.typeref.is_struct() || arg.typeref.is_anything())
.take_while(|arg| arg.typeref.is_option() || arg.typeref.is_struct() || arg.typeref.is_anything() || arg.variadic)
.count();

self.parameters.len() - num_optionals
Expand Down Expand Up @@ -2057,7 +2058,20 @@ impl<'a> TypeChecker<'a> {
.count();

// Verify arity
let arg_count = arg_list.pos_args.len() + (if arg_list.named_args.is_empty() { 0 } else { 1 });

// check if there is a variadic parameter, get its index
let variadic_index = func_sig.parameters.iter().position(|o| o.variadic);
let (index_last_item, arg_count) = if let Some(variadic_index) = variadic_index {
(
variadic_index,
(variadic_index + 1) + (if arg_list.named_args.is_empty() { 0 } else { 1 }),
)
} else {
(
arg_list_types.pos_args.len(),
(arg_list_types.pos_args.len()) + (if arg_list.named_args.is_empty() { 0 } else { 1 }),
)
};
let min_args = func_sig.parameters.len() - num_optionals;
let max_args = func_sig.parameters.len();
if arg_count < min_args || arg_count > max_args {
Expand All @@ -2076,10 +2090,41 @@ impl<'a> TypeChecker<'a> {
.iter()
.take(func_sig.parameters.len() - num_optionals);

// Verify passed positional arguments match the function's parameter types
for (arg_expr, arg_type, param) in izip!(arg_list.pos_args.iter(), arg_list_types.pos_args.iter(), params) {
self.validate_type(*arg_type, param.typeref, arg_expr);
}
if index_last_item == arg_list_types.pos_args.len() {
for (arg_expr, arg_type, param) in izip!(arg_list.pos_args.iter(), arg_list_types.pos_args.iter(), params) {
self.validate_type(*arg_type, param.typeref, arg_expr);
}
} else {
let mut new_arg_list: Vec<&Expr> = Vec::new();
let mut new_arg_list_types: Vec<TypeRef> = Vec::new();
for i in 0..index_last_item {
new_arg_list.push(arg_list.pos_args.get(i).unwrap());
new_arg_list_types.push(*arg_list_types.pos_args.get(i).unwrap());
}

let mut variadic_arg_list: Vec<&Expr> = Vec::new();
let variadic_arg_types = *arg_list_types.pos_args.get(index_last_item).unwrap();
for i in index_last_item..arg_list.pos_args.len() {
let variadic_arg = arg_list.pos_args.get(i).unwrap();
if !variadic_arg_types.is_same_type_as(arg_list_types.pos_args.get(i).unwrap()) {
let error = format!(
"Expected type to be {}, but got {} instead.",
variadic_arg_types,
arg_list_types.pos_args.get(i).unwrap()
);
self.spanned_error(&variadic_arg.span, error);
}
variadic_arg_list.push(variadic_arg);
}
let variadic_array_inner_type = *arg_list_types.pos_args.get(index_last_item).unwrap();
for (arg_expr, arg_type, param) in izip!(new_arg_list.iter(), new_arg_list_types.iter(), params) {
self.validate_type(*arg_type, param.typeref, *arg_expr);
}
// assert that each the extra args are of the same type as the variadic array type
for arg_expr in variadic_arg_list.iter() {
self.validate_type(variadic_array_inner_type, variadic_array_inner_type, *arg_expr);
}
};
None
}

Expand Down Expand Up @@ -2288,11 +2333,31 @@ impl<'a> TypeChecker<'a> {
}
TypeAnnotationKind::Function(ast_sig) => {
let mut parameters = vec![];
for i in 0..ast_sig.parameters.len() {
let p = ast_sig.parameters.get(i).unwrap();
if p.variadic && i != (ast_sig.parameters.len() - 1) {
self.spanned_error(
&ast_sig.parameters.get(i).unwrap().name.span,
"Variadic parameters must always be the last parameter in a function.".to_string(),
);
}

if p.variadic {
match &p.type_annotation.kind {
TypeAnnotationKind::Array(_) | TypeAnnotationKind::MutArray(_) => {}
_ => self.spanned_error(
&ast_sig.parameters.get(i).unwrap().name.span,
"Variadic parameters must be type Array or MutArray.".to_string(),
),
};
}
}
for p in ast_sig.parameters.iter() {
parameters.push(FunctionParameter {
name: p.name.name.clone(),
typeref: self.resolve_type_annotation(&p.type_annotation, env),
docs: Docs::default(),
variadic: p.variadic,
});
}
let sig = FunctionSignature {
Expand Down Expand Up @@ -3554,6 +3619,7 @@ impl<'a> TypeChecker<'a> {
name: param.name.clone(),
docs: param.docs.clone(),
typeref: self.get_concrete_type_for_generic(param.typeref, &types_map),
variadic: param.variadic,
})
.collect();

Expand Down Expand Up @@ -4514,6 +4580,7 @@ mod tests {
typeref: num,
docs: Docs::default(),
name: "p1".into(),
variadic: false,
}],
void,
Phase::Inflight,
Expand All @@ -4523,6 +4590,7 @@ mod tests {
typeref: string,
docs: Docs::default(),
name: "p1".into(),
variadic: false,
}],
void,
Phase::Inflight,
Expand Down Expand Up @@ -4561,6 +4629,7 @@ mod tests {
typeref: string,
docs: Docs::default(),
name: "p1".into(),
variadic: false,
}],
void,
Phase::Inflight,
Expand All @@ -4570,6 +4639,7 @@ mod tests {
typeref: opt_string,
docs: Docs::default(),
name: "p1".into(),
variadic: false,
}],
void,
Phase::Inflight,
Expand Down
2 changes: 2 additions & 0 deletions libs/wingc/src/type_check/jsii_importer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,7 @@ impl<'a> JsiiImporter<'a> {
name: param.name.clone(),
typeref: self.parameter_to_wing_type(&param),
docs: Docs::from(&param.docs),
variadic: param.variadic.unwrap_or(false),
});
}
}
Expand Down Expand Up @@ -726,6 +727,7 @@ impl<'a> JsiiImporter<'a> {
name: param.name.clone(),
typeref: self.parameter_to_wing_type(&param),
docs: Docs::from(&param.docs),
variadic: param.variadic.unwrap_or(false),
});
}
}
Expand Down
Loading

0 comments on commit d5e8bd8

Please sign in to comment.