Skip to content
Open
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
5 changes: 5 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@

* motoko (`moc`)

* Add (caffeine) warning `M0238` (#5597).
Warns if type annotations are used on anonymous functions.
Used to avoid unnecessary type annotations and rely on explicit type instantiations to propagate the type information instead.
(allowed by default, warn with `-W 0238`).

* Add (caffeine) warning `M0237` (#5588).
Warns if explicit argument could have been inferred and omitted,
e.g. `a.sort(Nat.compare)` vs `a.sort()`.
Expand Down
1 change: 1 addition & 0 deletions src/lang_utils/error_codes.ml
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ let warning_codes = [
"M0235", None, "Deprecate for caffeine";
"M0236", None, "Suggest contextual dot notation";
"M0237", None, "Suggest redundant explicit arguments";
"M0238", None, "Anonymous function with type annotations";
]

let try_find_explanation code =
Expand Down
1 change: 1 addition & 0 deletions src/mo_config/flags.ml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ let default_warning_levels = M.empty
|> M.add "M0235" Allow (* don't deprecate for non-caffeine *)
|> M.add "M0236" Allow (* don't suggest contextual dot notation *)
|> M.add "M0237" Allow (* don't report redundant explicit arguments *)
|> M.add "M0238" Allow (* allow type-annotations on anonymous functions *)

let warning_levels = ref default_warning_levels

Expand Down
3 changes: 2 additions & 1 deletion src/mo_def/syntax.ml
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,8 @@ open Source
(* Identifiers *)

let anon_id sort at = "@anon-" ^ sort ^ "-" ^ string_of_pos at.left
let is_anon_id id = Lib.String.chop_prefix "@anon-" id.it <> None
let is_anon_string str = Lib.String.chop_prefix "@anon-" str <> None
let is_anon_id id = is_anon_string id.it

let is_privileged name =
String.length name > 0 && name.[0] = '@'
Expand Down
34 changes: 28 additions & 6 deletions src/mo_frontend/typing.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1123,6 +1123,21 @@ and is_explicit_dec d =
List.for_all (fun (df : dec_field) -> is_explicit_dec df.it.dec) dfs
| IncludeD (_, e, _) -> is_explicit_exp e

(* Check if a pattern has a type annotation *)
let rec has_annot p =
match p.it with
| WildP | VarP _ -> false
| LitP l | SignP (_, l) -> false
| OptP p1 | TagP (_, p1) | ParP p1 -> has_annot p1
| TupP ps -> List.exists has_annot ps
| ObjP pfs -> List.exists has_annot_field pfs
| AltP (p1, p2) -> has_annot p1 || has_annot p2
| AnnotP _ -> true

and has_annot_field pf =
match pf.it with
| ValPF(_, p) -> has_annot p
| TypPF(_) -> false

(* Literals *)

Expand Down Expand Up @@ -1886,14 +1901,15 @@ and infer_exp'' env exp : T.typ =
"expected array type or Blob, but expression produces type%a"
display_typ_expand t1
end
| FuncE (_, shared_pat, typ_binds, pat, typ_opt, _sugar, exp1) ->
| FuncE (func_name, shared_pat, typ_binds, pat, typ_opt, _sugar, exp1) ->
if not env.pre && not in_actor && T.is_shared_sort shared_pat.it then begin
error_in [Flags.WASIMode; Flags.WasmMode] env exp1.at "M0076"
"shared functions are not supported";
if not in_actor then
error_in [Flags.ICMode; Flags.RefMode] env exp1.at "M0077"
"a shared function is only allowed as a public field of an actor";
end;
warn_typed_anonymous_function env exp1.at func_name pat typ_opt;
let typ = match typ_opt with
| Some typ -> typ
| None -> {it = TupT []; at = no_region; note = T.Pre}
Expand Down Expand Up @@ -2510,8 +2526,8 @@ and check_exp' env0 t exp : T.typ =
Option.iter (check_exp_strong { env with async = C.NullCap; rets = None; labs = T.Env.empty; } T.unit) exp2_opt;
t
(* TODO: allow shared with one scope par *)
| FuncE (_, shared_pat, [], pat, typ_opt, _sugar, exp), T.Func (s, c, [], ts1, ts2) ->
let env', t2, codom = check_func_step env0.in_actor env (shared_pat, pat, typ_opt, exp) (s, c, ts1, ts2) in
| FuncE (func_name, shared_pat, [], pat, typ_opt, _sugar, exp), T.Func (s, c, [], ts1, ts2) ->
let env', t2, codom = check_func_step env0.in_actor env (func_name, shared_pat, pat, typ_opt, exp) (s, c, ts1, ts2) in
if not (sub env Source.no_region t2 codom) then
error env exp.at "M0095"
"function return type%a\ndoes not match expected return type%a"
Expand Down Expand Up @@ -2566,18 +2582,24 @@ and check_exp_field env (ef : exp_field) fts =
| None ->
ignore (infer_exp env exp)

and warn_typed_anonymous_function env at func_name pat typ_opt =
if not env.pre && Flags.get_warning_level "M0238" <> Flags.Allow && is_anon_string func_name
&& (Option.is_some typ_opt || has_annot pat) then
warn env at "M0238" "Avoid type annotations on anonymous functions";

(** Performs the first step of checking that the given [FuncE (_, shared_pat, [], pat, typ_opt, _, exp)] expression has type [T.Func (s, c, [], ts1, ts2)].
Used to prepare the new env for checking the [exp] (body of the function).
Returns:
- the env for the body of the function ([exp]),
- [exp_typ], the expected type of the body,
- [codom], the codomain of the function (built from [ts2]). The caller must check that [sub exp_typ codom].
*)
and check_func_step in_actor env (shared_pat, pat, typ_opt, exp) (s, c, ts1, ts2) : env * T.typ * T.typ =
and check_func_step in_actor env (func_name, shared_pat, pat, typ_opt, exp) (s, c, ts1, ts2) : env * T.typ * T.typ =
let sort, ve = check_shared_pat env shared_pat in
if not env.pre && not in_actor && T.is_shared_sort sort then
error_in [Flags.ICMode; Flags.RefMode] env exp.at "M0077"
"a shared function is only allowed as a public field of an actor";
warn_typed_anonymous_function env exp.at func_name pat typ_opt;
let ve1 = check_pat_exhaustive (if T.is_shared_sort sort then local_error else warn) env (T.seq ts1) pat in
let ve2 = T.Env.adjoin ve ve1 in
let codom = T.codom c (fun () -> assert false) ts2 in
Expand Down Expand Up @@ -2945,11 +2967,11 @@ and infer_call_instantiation env t1 ctx_dot tbs t_arg t_ret exp2 at t_expect_opt
(* Substitute fixed type variables *)
let typ = T.open_ ts typ in
match exp.it, T.normalize typ with
| FuncE (_, shared_pat, [], pat, typ_opt, _, body), T.Func (s, c, [], ts1, ts2) ->
| FuncE (func_name, shared_pat, [], pat, typ_opt, _, body), T.Func (s, c, [], ts1, ts2) ->
(* Check that all type variables in the function input type are fixed, fail otherwise *)
Bi_match.fail_when_types_are_not_closed remaining ts1;
(* Check the function input type and prepare for inferring the body *)
let env', body_typ, codom = check_func_step false env (shared_pat, pat, typ_opt, body) (s, c, ts1, ts2) in
let env', body_typ, codom = check_func_step false env (func_name, shared_pat, pat, typ_opt, body) (s, c, ts1, ts2) in
(* [codom] comes from [ts2] which might contain unsolved type variables. *)
let closed = Bi_match.is_closed remaining codom in
if not env.pre && (closed || body_typ <> codom) then begin
Expand Down
1 change: 1 addition & 0 deletions test/fail/ok/warn-help.tc.ok
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,6 @@ M0223 (A) Redundant type instantiation
M0235 (A) Deprecate for caffeine
M0236 (A) Suggest contextual dot notation
M0237 (A) Suggest redundant explicit arguments
M0238 (A) Anonymous function with type annotations

Legend: A - allowed (warning disabled); W - warn (warning enabled); E - error (treated as error)
4 changes: 4 additions & 0 deletions test/run/ok/warn-typed-anonymous-func.tc.ok
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
warn-typed-anonymous-func.mo:9.47-9.52: warning [M0238], Avoid type annotations on anonymous functions
warn-typed-anonymous-func.mo:10.35-10.40: warning [M0238], Avoid type annotations on anonymous functions
warn-typed-anonymous-func.mo:11.35-11.40: warning [M0238], Avoid type annotations on anonymous functions
warn-typed-anonymous-func.mo:12.35-12.40: warning [M0238], Avoid type annotations on anonymous functions
12 changes: 12 additions & 0 deletions test/run/warn-typed-anonymous-func.mo
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//MOC-FLAG -W M0238
let n = 1;

func foo<T>(x : T, f : (T, T) -> T) : T = f(x, x);

ignore foo(n, func (x, y) = x + y); // no warning

// With Warnings
ignore foo(n, func (x : Nat, y : Nat) : Nat = x + y);
ignore foo(n, func (x, y) : Nat = x + y);
ignore foo(n, func (x : Nat, y) = x + y);
ignore foo(n, func (x, y : Nat) = x + y);