Skip to content

Commit 583014c

Browse files
orizidean-starkware
authored andcommitted
Inferring closures types within higher order functions
1 parent 739ecc2 commit 583014c

File tree

13 files changed

+210
-71
lines changed

13 files changed

+210
-71
lines changed

corelib/src/test/option_test.cairo

+1-1
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ fn test_default_for_option() {
209209
#[test]
210210
fn test_option_some_map() {
211211
let maybe_some_string: Option<ByteArray> = Option::Some("Hello, World!");
212-
let maybe_some_len = maybe_some_string.map(|s: ByteArray| s.len());
212+
let maybe_some_len = maybe_some_string.map(|s| s.len());
213213
assert!(maybe_some_len == Option::Some(13));
214214
}
215215

crates/cairo-lang-lowering/src/lower/test_data/for

+4-4
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ Statements:
4545
(v16: core::array::Array::<core::felt252>, v17: @core::array::Array::<core::felt252>) <- snapshot(v15)
4646
(v18: core::array::Span::<core::felt252>) <- core::array::ArrayToSpan::<core::felt252>::span(v17)
4747
(v19: core::array::SpanIter::<core::felt252>) <- core::array::SpanIntoIterator::<core::felt252>::into_iter(v18)
48-
(v21: core::array::SpanIter::<core::felt252>, v22: core::felt252, v20: ()) <- test::foo[expr30](v19, v0, v2)
48+
(v21: core::array::SpanIter::<core::felt252>, v22: core::felt252, v20: ()) <- test::foo[expr38](v19, v0, v2)
4949
End:
5050
Return(v22)
5151

@@ -69,7 +69,7 @@ Statements:
6969
(v14: core::array::Array::<core::felt252>, v15: @core::array::Array::<core::felt252>) <- snapshot(v10)
7070
(v16: core::array::Span::<core::felt252>) <- struct_construct(v15)
7171
(v17: core::array::SpanIter::<core::felt252>) <- struct_construct(v16)
72-
(v18: core::RangeCheck, v19: core::gas::GasBuiltin, v20: core::panics::PanicResult::<(core::array::SpanIter::<core::felt252>, core::felt252, ())>) <- test::foo[expr30](v0, v1, v17, v11, v13)
72+
(v18: core::RangeCheck, v19: core::gas::GasBuiltin, v20: core::panics::PanicResult::<(core::array::SpanIter::<core::felt252>, core::felt252, ())>) <- test::foo[expr38](v0, v1, v17, v11, v13)
7373
End:
7474
Match(match_enum(v20) {
7575
PanicResult::Ok(v21) => blk1,
@@ -111,7 +111,7 @@ End:
111111
blk1:
112112
Statements:
113113
(v6: core::felt252) <- core::Felt252Add::add(v1, v2)
114-
(v8: core::array::SpanIter::<core::felt252>, v9: core::felt252, v7: ()) <- test::foo[expr30](v4, v6, v2)
114+
(v8: core::array::SpanIter::<core::felt252>, v9: core::felt252, v7: ()) <- test::foo[expr38](v4, v6, v2)
115115
End:
116116
Goto(blk3, {v9 -> v12, v8 -> v13, v7 -> v11})
117117

@@ -195,7 +195,7 @@ End:
195195
blk8:
196196
Statements:
197197
(v30: core::felt252) <- core::felt252_add(v3, v4)
198-
(v31: core::RangeCheck, v32: core::gas::GasBuiltin, v33: core::panics::PanicResult::<(core::array::SpanIter::<core::felt252>, core::felt252, ())>) <- test::foo[expr30](v5, v6, v27, v30, v4)
198+
(v31: core::RangeCheck, v32: core::gas::GasBuiltin, v33: core::panics::PanicResult::<(core::array::SpanIter::<core::felt252>, core::felt252, ())>) <- test::foo[expr38](v5, v6, v27, v30, v4)
199199
End:
200200
Return(v31, v32, v33)
201201

crates/cairo-lang-lowering/src/lower/test_data/loop

+10-10
Original file line numberDiff line numberDiff line change
@@ -1064,7 +1064,7 @@ Statements:
10641064
(v1: test::T) <- struct_construct(v0)
10651065
(v2: test::S) <- struct_destructure(v1)
10661066
(v3: test::S, v4: @test::S) <- snapshot(v2)
1067-
(v5: ()) <- test::foo[expr7](v4)
1067+
(v5: ()) <- test::foo[expr8](v4)
10681068
(v6: ()) <- test::STT::f1oo(v4)
10691069
(v7: ()) <- struct_construct()
10701070
End:
@@ -1158,7 +1158,7 @@ Statements:
11581158
(v4: test::B, v5: @test::B) <- snapshot(v3)
11591159
(v6: core::integer::u32) <- struct_destructure(v4)
11601160
(v7: test::B) <- struct_construct(v6)
1161-
(v8: ()) <- test::foo[expr16](v6, v5)
1161+
(v8: ()) <- test::foo[expr18](v6, v5)
11621162
(v9: ()) <- struct_construct()
11631163
End:
11641164
Return(v9)
@@ -1214,7 +1214,7 @@ Parameters: v0: core::integer::u32, v1: @test::B
12141214
blk0 (root):
12151215
Statements:
12161216
() <- test::ex1(v1)
1217-
(v2: ()) <- test::foo[expr14](v0)
1217+
(v2: ()) <- test::foo[expr16](v0)
12181218
(v3: ()) <- struct_construct()
12191219
End:
12201220
Return(v3)
@@ -1282,7 +1282,7 @@ Statements:
12821282
(v1: test::B) <- struct_construct(v0)
12831283
(v2: test::A) <- struct_construct(v1)
12841284
(v3: test::B) <- struct_destructure(v2)
1285-
(v4: ()) <- test::foo[expr16](v3)
1285+
(v4: ()) <- test::foo[expr18](v3)
12861286
(v5: ()) <- struct_construct()
12871287
End:
12881288
Return(v5)
@@ -1339,7 +1339,7 @@ Statements:
13391339
() <- test::ex1(v0)
13401340
(v1: core::integer::u32) <- struct_destructure(v0)
13411341
(v2: core::integer::u32, v3: @core::integer::u32) <- snapshot(v1)
1342-
(v4: ()) <- test::foo[expr14](v3)
1342+
(v4: ()) <- test::foo[expr16](v3)
13431343
(v5: ()) <- struct_construct()
13441344
End:
13451345
Return(v5)
@@ -1399,7 +1399,7 @@ Statements:
13991399
(v0: core::felt252) <- 0
14001400
(v1: test::A) <- struct_construct(v0)
14011401
(v2: core::felt252) <- 0
1402-
(v4: test::A, v5: core::felt252, v3: ()) <- test::foo[expr19](v2, v1)
1402+
(v4: test::A, v5: core::felt252, v3: ()) <- test::foo[expr20](v2, v1)
14031403
(v6: ()) <- struct_construct()
14041404
End:
14051405
Return(v6)
@@ -1412,7 +1412,7 @@ Statements:
14121412
(v2: core::felt252) <- 0
14131413
(v3: core::felt252) <- 0
14141414
(v4: test::A) <- struct_construct(v2)
1415-
(v5: core::RangeCheck, v6: core::gas::GasBuiltin, v7: core::panics::PanicResult::<(test::A, core::felt252, ())>) <- test::foo[expr19](v0, v1, v3, v4)
1415+
(v5: core::RangeCheck, v6: core::gas::GasBuiltin, v7: core::panics::PanicResult::<(test::A, core::felt252, ())>) <- test::foo[expr20](v0, v1, v3, v4)
14161416
End:
14171417
Match(match_enum(v7) {
14181418
PanicResult::Ok(v8) => blk1,
@@ -1462,7 +1462,7 @@ Statements:
14621462
() <- test::use_a(v12)
14631463
(v13: core::felt252) <- 1
14641464
(v15: core::felt252, v14: ()) <- core::ops::arith::DeprecatedAddAssign::<core::felt252, core::Felt252AddEq>::add_assign(v2, v13)
1465-
(v17: test::A, v18: core::felt252, v16: ()) <- test::foo[expr19](v15, v11)
1465+
(v17: test::A, v18: core::felt252, v16: ()) <- test::foo[expr20](v15, v11)
14661466
End:
14671467
Goto(blk3, {v17 -> v21, v18 -> v22, v16 -> v20})
14681468

@@ -1513,7 +1513,7 @@ Statements:
15131513
() <- test::use_a(v16)
15141514
(v17: core::felt252) <- 1
15151515
(v18: core::felt252) <- core::felt252_add(v2, v17)
1516-
(v19: core::RangeCheck, v20: core::gas::GasBuiltin, v21: core::panics::PanicResult::<(test::A, core::felt252, ())>) <- test::foo[expr19](v4, v5, v18, v15)
1516+
(v19: core::RangeCheck, v20: core::gas::GasBuiltin, v21: core::panics::PanicResult::<(test::A, core::felt252, ())>) <- test::foo[expr20](v4, v5, v18, v15)
15171517
End:
15181518
Return(v19, v20, v21)
15191519

@@ -1584,7 +1584,7 @@ Parameters: v0: core::RangeCheck, v1: core::gas::GasBuiltin
15841584
blk0 (root):
15851585
Statements:
15861586
(v2: core::integer::u8) <- 0
1587-
(v3: core::RangeCheck, v4: core::gas::GasBuiltin, v5: core::panics::PanicResult::<(core::integer::u8, ())>) <- test::MyImpl::impl_in_trait[expr9](v0, v1, v2)
1587+
(v3: core::RangeCheck, v4: core::gas::GasBuiltin, v5: core::panics::PanicResult::<(core::integer::u8, ())>) <- test::MyImpl::impl_in_trait[expr10](v0, v1, v2)
15881588
End:
15891589
Match(match_enum(v5) {
15901590
PanicResult::Ok(v6) => blk1,

crates/cairo-lang-lowering/src/test_data/for

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ Statements:
3939
(v11: core::array::Array::<core::felt252>, v12: @core::array::Array::<core::felt252>) <- snapshot(v10)
4040
(v13: core::array::Span::<core::felt252>) <- struct_construct(v12)
4141
(v14: core::array::SpanIter::<core::felt252>) <- struct_construct(v13)
42-
(v15: core::RangeCheck, v16: core::gas::GasBuiltin, v17: core::panics::PanicResult::<(core::array::SpanIter::<core::felt252>, ())>) <- test::foo[expr26](v0, v1, v14)
42+
(v15: core::RangeCheck, v16: core::gas::GasBuiltin, v17: core::panics::PanicResult::<(core::array::SpanIter::<core::felt252>, ())>) <- test::foo[expr34](v0, v1, v14)
4343
End:
4444
Match(match_enum(v17) {
4545
PanicResult::Ok(v18) => blk1,

crates/cairo-lang-lowering/src/test_data/strings

+1-1
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ Statements:
201201
(v8: core::byte_array::ByteArray, v9: @core::byte_array::ByteArray) <- snapshot(v7)
202202
(v10: @core::array::Array::<core::bytes_31::bytes31>, v11: @core::felt252, v12: @core::integer::u32) <- struct_destructure(v9)
203203
(v13: core::array::Span::<core::bytes_31::bytes31>) <- struct_construct(v10)
204-
(v14: core::RangeCheck, v15: core::gas::GasBuiltin, v16: core::panics::PanicResult::<(core::array::Span::<core::bytes_31::bytes31>, core::array::Array::<core::bytes_31::bytes31>, ())>) <- core::array::ArrayTCloneImpl::clone[expr14](v0, v1, v13, v4)
204+
(v14: core::RangeCheck, v15: core::gas::GasBuiltin, v16: core::panics::PanicResult::<(core::array::Span::<core::bytes_31::bytes31>, core::array::Array::<core::bytes_31::bytes31>, ())>) <- core::array::ArrayTCloneImpl::clone[expr15](v0, v1, v13, v4)
205205
End:
206206
Match(match_enum(v16) {
207207
PanicResult::Ok(v17) => blk1,

crates/cairo-lang-semantic/src/db.rs

+17-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ use crate::diagnostic::SemanticDiagnosticKind;
2626
use crate::expr::inference::{self, ImplVar, ImplVarId};
2727
use crate::items::constant::{ConstCalcInfo, ConstValueId, Constant, ImplConstantId};
2828
use crate::items::function_with_body::FunctionBody;
29-
use crate::items::functions::{ImplicitPrecedence, InlineConfiguration};
29+
use crate::items::functions::{GenericFunctionId, ImplicitPrecedence, InlineConfiguration};
3030
use crate::items::generics::{GenericParam, GenericParamData, GenericParamsData};
3131
use crate::items::imp::{
3232
ImplId, ImplImplId, ImplLookupContext, ImplicitImplImplData, UninferredImpl,
@@ -1431,6 +1431,22 @@ pub trait SemanticGroup:
14311431
#[salsa::invoke(items::functions::concrete_function_signature)]
14321432
fn concrete_function_signature(&self, function_id: FunctionId) -> Maybe<semantic::Signature>;
14331433

1434+
/// Returns a mapping of closure types to their associated parameter types for a concrete
1435+
/// function.
1436+
#[salsa::invoke(items::functions::concrete_function_closure_params)]
1437+
fn concrete_function_closure_params(
1438+
&self,
1439+
function_id: FunctionId,
1440+
) -> Maybe<OrderedHashMap<semantic::TypeId, semantic::TypeId>>;
1441+
1442+
/// Returns a mapping of closure types to their associated parameter types for a generic
1443+
/// function.
1444+
#[salsa::invoke(items::functions::get_closure_params)]
1445+
fn get_closure_params(
1446+
&self,
1447+
generic_function_id: GenericFunctionId,
1448+
) -> Maybe<OrderedHashMap<TypeId, TypeId>>;
1449+
14341450
// Generic type.
14351451
// =============
14361452
/// Returns the generic params of a generic type.

crates/cairo-lang-semantic/src/expr/compute.rs

+65-17
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use cairo_lang_filesystem::ids::{FileKind, FileLongId, VirtualFile};
2222
use cairo_lang_proc_macros::DebugWithDb;
2323
use cairo_lang_syntax::node::ast::{
2424
BinaryOperator, BlockOrIf, ClosureParamWrapper, ExprPtr, OptionReturnTypeClause, PatternListOr,
25-
PatternStructParam, UnaryOperator,
25+
PatternStructParam, TerminalIdentifier, UnaryOperator,
2626
};
2727
use cairo_lang_syntax::node::db::SyntaxGroup;
2828
use cairo_lang_syntax::node::helpers::{GetIdentifier, PathSegmentEx};
@@ -66,7 +66,7 @@ use crate::expr::inference::{ImplVarTraitItemMappings, InferenceId};
6666
use crate::items::constant::{ConstValue, resolve_const_expr_and_evaluate, validate_const_expr};
6767
use crate::items::enm::SemanticEnumEx;
6868
use crate::items::feature_kind::extract_item_feature_config;
69-
use crate::items::functions::function_signature_params;
69+
use crate::items::functions::{concrete_function_closure_params, function_signature_params};
7070
use crate::items::imp::{ImplLookupContext, filter_candidate_traits, infer_impl_by_self};
7171
use crate::items::modifiers::compute_mutability;
7272
use crate::items::us::get_use_path_segments;
@@ -424,7 +424,7 @@ pub fn maybe_compute_expr_semantic(
424424
ast::Expr::Indexed(expr) => compute_expr_indexed_semantic(ctx, expr),
425425
ast::Expr::FixedSizeArray(expr) => compute_expr_fixed_size_array_semantic(ctx, expr),
426426
ast::Expr::For(expr) => compute_expr_for_semantic(ctx, expr),
427-
ast::Expr::Closure(expr) => compute_expr_closure_semantic(ctx, expr),
427+
ast::Expr::Closure(expr) => compute_expr_closure_semantic(ctx, expr, None),
428428
}
429429
}
430430

@@ -882,7 +882,7 @@ fn compute_expr_function_call_semantic(
882882
let mut arg_types = vec![];
883883
for arg_syntax in args_iter {
884884
let stable_ptr = arg_syntax.stable_ptr();
885-
let arg = compute_named_argument_clause(ctx, arg_syntax);
885+
let arg = compute_named_argument_clause(ctx, arg_syntax, None);
886886
if arg.2 != Mutability::Immutable {
887887
return Err(ctx.diagnostics.report(stable_ptr, RefClosureArgument));
888888
}
@@ -930,7 +930,7 @@ fn compute_expr_function_call_semantic(
930930
let named_args: Vec<_> = args_syntax
931931
.elements(syntax_db)
932932
.into_iter()
933-
.map(|arg_syntax| compute_named_argument_clause(ctx, arg_syntax))
933+
.map(|arg_syntax| compute_named_argument_clause(ctx, arg_syntax, None))
934934
.collect();
935935
if named_args.len() != 1 {
936936
return Err(ctx.diagnostics.report(syntax, WrongNumberOfArguments {
@@ -979,16 +979,21 @@ fn compute_expr_function_call_semantic(
979979
let mut args_iter = args_syntax.elements(syntax_db).into_iter();
980980
// Normal parameters
981981
let mut named_args = vec![];
982-
for _ in function_parameter_types(ctx, function)? {
982+
let closure_params = concrete_function_closure_params(db, function)?;
983+
for ty in function_parameter_types(ctx, function)? {
983984
let Some(arg_syntax) = args_iter.next() else {
984985
continue;
985986
};
986-
named_args.push(compute_named_argument_clause(ctx, arg_syntax));
987+
named_args.push(compute_named_argument_clause(
988+
ctx,
989+
arg_syntax,
990+
closure_params.get(&ty).cloned(),
991+
));
987992
}
988993

989994
// Maybe coupon
990995
if let Some(arg_syntax) = args_iter.next() {
991-
named_args.push(compute_named_argument_clause(ctx, arg_syntax));
996+
named_args.push(compute_named_argument_clause(ctx, arg_syntax, None));
992997
}
993998

994999
expr_function_call(ctx, function, named_args, syntax, syntax.stable_ptr().into())
@@ -1006,6 +1011,7 @@ fn compute_expr_function_call_semantic(
10061011
pub fn compute_named_argument_clause(
10071012
ctx: &mut ComputationContext<'_>,
10081013
arg_syntax: ast::Arg,
1014+
closure_param_types: Option<TypeId>,
10091015
) -> NamedArg {
10101016
let syntax_db = ctx.db.upcast();
10111017

@@ -1017,11 +1023,16 @@ pub fn compute_named_argument_clause(
10171023

10181024
let arg_clause = arg_syntax.arg_clause(syntax_db);
10191025
let (expr, arg_name_identifier) = match arg_clause {
1020-
ast::ArgClause::Unnamed(arg_unnamed) => {
1021-
(compute_expr_semantic(ctx, &arg_unnamed.value(syntax_db)), None)
1022-
}
1023-
ast::ArgClause::Named(arg_named) => (
1024-
compute_expr_semantic(ctx, &arg_named.value(syntax_db)),
1026+
ast::ArgClause::Unnamed(arg_unnamed) => handle_possible_closure_expr(
1027+
ctx,
1028+
&arg_unnamed.value(syntax_db),
1029+
closure_param_types,
1030+
None,
1031+
),
1032+
ast::ArgClause::Named(arg_named) => handle_possible_closure_expr(
1033+
ctx,
1034+
&arg_named.value(syntax_db),
1035+
closure_param_types,
10251036
Some(arg_named.name(syntax_db)),
10261037
),
10271038
ast::ArgClause::FieldInitShorthand(arg_field_init_shorthand) => {
@@ -1034,10 +1045,32 @@ pub fn compute_named_argument_clause(
10341045
(expr, Some(arg_name_identifier))
10351046
}
10361047
};
1037-
10381048
NamedArg(expr, arg_name_identifier, mutability)
10391049
}
10401050

1051+
/// Handles the semantic computation of a closure expression.
1052+
/// It processes a closure expression, computes its semantic model,
1053+
/// allocates it in the expression arena, and ensures that the closure's
1054+
/// parameter types are conformed if provided.
1055+
fn handle_possible_closure_expr(
1056+
ctx: &mut ComputationContext<'_>,
1057+
expr: &ast::Expr,
1058+
closure_param_types: Option<TypeId>,
1059+
arg_name: Option<TerminalIdentifier>,
1060+
) -> (ExprAndId, Option<TerminalIdentifier>) {
1061+
if let ast::Expr::Closure(expr_closure) = expr {
1062+
let expr = compute_expr_closure_semantic(ctx, expr_closure, closure_param_types);
1063+
let expr = wrap_maybe_with_missing(ctx, expr, expr_closure.stable_ptr().into());
1064+
let id = ctx.arenas.exprs.alloc(expr.clone());
1065+
(ExprAndId { expr, id }, arg_name)
1066+
} else {
1067+
let expr = compute_expr_semantic(ctx, expr);
1068+
let expr = wrap_maybe_with_missing(ctx, Ok(expr.expr.clone()), expr.stable_ptr());
1069+
let id = ctx.arenas.exprs.alloc(expr.clone());
1070+
(ExprAndId { expr, id }, arg_name)
1071+
}
1072+
}
1073+
10411074
pub fn compute_root_expr(
10421075
ctx: &mut ComputationContext<'_>,
10431076
syntax: &ast::ExprBlock,
@@ -1645,6 +1678,7 @@ fn compute_loop_body_semantic(
16451678
fn compute_expr_closure_semantic(
16461679
ctx: &mut ComputationContext<'_>,
16471680
syntax: &ast::ExprClosure,
1681+
param_types: Option<TypeId>,
16481682
) -> Maybe<Expr> {
16491683
ctx.are_closures_in_context = true;
16501684
let syntax_db = ctx.db.upcast();
@@ -1663,6 +1697,14 @@ fn compute_expr_closure_semantic(
16631697
} else {
16641698
vec![]
16651699
};
1700+
let closure_type =
1701+
TypeLongId::Tuple(params.iter().map(|param| param.ty).collect()).intern(new_ctx.db);
1702+
if let Some(param_types) = param_types {
1703+
if let Err(err_set) = new_ctx.resolver.inference().conform_ty(closure_type, param_types)
1704+
{
1705+
new_ctx.resolver.inference().consume_error_without_reporting(err_set);
1706+
}
1707+
}
16661708

16671709
params.iter().filter(|param| param.mutability == Mutability::Reference).for_each(|param| {
16681710
new_ctx.diagnostics.report(param.stable_ptr(ctx.db.upcast()), RefClosureParam);
@@ -2834,16 +2876,22 @@ fn method_call_expr(
28342876
// Self argument.
28352877
let mut named_args = vec![NamedArg(fixed_lexpr, None, mutability)];
28362878
// Other arguments.
2837-
for _ in function_parameter_types(ctx, function_id)?.skip(1) {
2879+
let closure_params: OrderedHashMap<TypeId, TypeId> =
2880+
concrete_function_closure_params(ctx.db, function_id)?;
2881+
for ty in function_parameter_types(ctx, function_id)?.skip(1) {
28382882
let Some(arg_syntax) = args_iter.next() else {
28392883
break;
28402884
};
2841-
named_args.push(compute_named_argument_clause(ctx, arg_syntax));
2885+
named_args.push(compute_named_argument_clause(
2886+
ctx,
2887+
arg_syntax,
2888+
closure_params.get(&ty).cloned(),
2889+
));
28422890
}
28432891

28442892
// Maybe coupon
28452893
if let Some(arg_syntax) = args_iter.next() {
2846-
named_args.push(compute_named_argument_clause(ctx, arg_syntax));
2894+
named_args.push(compute_named_argument_clause(ctx, arg_syntax, None));
28472895
}
28482896

28492897
expr_function_call(ctx, function_id, named_args, &expr, stable_ptr)

crates/cairo-lang-semantic/src/expr/test_data/closure

+20
Original file line numberDiff line numberDiff line change
@@ -768,3 +768,23 @@ error: Closure parameters cannot be references
768768
--> lib.cairo:2:14
769769
let _ = |ref a| {
770770
^^^^^
771+
772+
//! > ==========================================================================
773+
774+
//! > Passing closures as args with less explicit typing.
775+
776+
//! > test_runner_name
777+
test_function_diagnostics(expect_diagnostics: false)
778+
779+
//! > function
780+
fn foo() -> Option<u32> {
781+
let x: Option<Array<i32>> = Option::Some(array![1, 2, 3]);
782+
x.map(|x| x.len())
783+
}
784+
785+
//! > function_name
786+
foo
787+
788+
//! > module_code
789+
790+
//! > expected_diagnostics

0 commit comments

Comments
 (0)