(**************************************************************************)
(*                                                                        *)
(*                                 OCaml                                  *)
(*                                                                        *)
(*                   Jeremie Dimino, Jane Street Europe                   *)
(*                                                                        *)
(*   Copyright 2015 Jane Street Group LLC                                 *)
(*                                                                        *)
(*   All rights reserved.  This file is distributed under the terms of    *)
(*   the GNU Lesser General Public License version 2.1, with the          *)
(*   special exception on linking described in the file LICENSE.          *)
(*                                                                        *)
(**************************************************************************)

open Asttypes
open Parsetree
open Ast_iterator

let err = Syntaxerr.ill_formed_ast

let empty_record loc = err loc "Records cannot be empty."
let invalid_tuple loc = err loc "Tuples must have at least 2 components."
let empty_open_tuple_pat loc =
  err loc "Open tuple patterns must have at least one component."
let short_closed_tuple_pat loc =
  err loc "Closed tuple patterns must have at least two components."
let no_args loc = err loc "Function application with no argument."
let empty_let loc = err loc "Let with no bindings."
let empty_type loc = err loc "Type declarations cannot be empty."
let empty_poly_binder loc =
  err loc "Explicit universal type quantification cannot be empty."
let complex_id loc = err loc "Functor application not allowed here."
let module_type_substitution_missing_rhs loc =
  err loc "Module type substitution with no right hand side"
let function_without_value_parameters loc =
  err loc "Function without any value parameters"
let invalid_struct_item loc =
  err loc "This kind of structure item is not allowed in this context."

let optional_label_on_functor loc =
  err loc "Optional argument for a module dependent function."

let simple_longident id =
  let rec is_simple = function
    | Longident.Lident _ -> true
    | Longident.Ldot (id, _) -> is_simple id.txt
    | Longident.Lapply _ -> false
  in
  if not (is_simple id.txt) then complex_id id.loc

let not_optional_label loc l =
  let is_optional =
    match l with
    | Optional _ -> true
    | _ -> false
  in
  if is_optional then optional_label_on_functor loc

let iterator =
  let super = Ast_iterator.default_iterator in
  let type_declaration self td =
    super.type_declaration self td;
    let loc = td.ptype_loc in
    match td.ptype_kind with
    | Ptype_record [] -> empty_record loc
    | _ -> ()
  in
  let typ self ty =
    super.typ self ty;
    let loc = ty.ptyp_loc in
    match ty.ptyp_desc with
    | Ptyp_tuple ([] | [_]) -> invalid_tuple loc
    | Ptyp_package ptyp ->
      List.iter (fun (id, _) -> simple_longident id) ptyp.ppt_constraints
    | Ptyp_functor  (l, _, ptyp, _) ->
      not_optional_label loc l;
      List.iter (fun (id, _) -> simple_longident id) ptyp.ppt_constraints
    | Ptyp_poly([],_) -> empty_poly_binder loc
    | _ -> ()
  in
  let pat self pat =
    begin match pat.ppat_desc with
    | Ppat_construct (_, Some (_, ({ppat_desc = Ppat_tuple _} as p)))
      when Builtin_attributes.explicit_arity pat.ppat_attributes ->
        super.pat self p (* allow unary tuple, see GPR#523. *)
    | _ ->
        super.pat self pat
    end;
    let loc = pat.ppat_loc in
    match pat.ppat_desc with
    | Ppat_tuple (([] | [_]), Closed) -> short_closed_tuple_pat loc
    | Ppat_tuple ([], Open) -> empty_open_tuple_pat loc
    | Ppat_record ([], _) -> empty_record loc
    | Ppat_construct (id, _) -> simple_longident id
    | Ppat_record (fields, _) ->
      List.iter (fun (id, _) -> simple_longident id) fields
    | _ -> ()
  in
  let expr self exp =
    begin match exp.pexp_desc with
    | Pexp_construct (_, Some ({pexp_desc = Pexp_tuple _} as e))
      when Builtin_attributes.explicit_arity exp.pexp_attributes ->
        super.expr self e (* allow unary tuple, see GPR#523. *)
    | _ ->
        super.expr self exp
    end;
    let loc = exp.pexp_loc in
    match exp.pexp_desc with
    | Pexp_tuple ([] | [_]) -> invalid_tuple loc
    | Pexp_record ([], _) -> empty_record loc
    | Pexp_apply (_, []) -> no_args loc
    | Pexp_let (_, [], _) -> empty_let loc
    | Pexp_ident id
    | Pexp_construct (id, _)
    | Pexp_field (_, id)
    | Pexp_setfield (_, id, _)
    | Pexp_new id -> simple_longident id
    | Pexp_record (fields, _) ->
      List.iter (fun (id, _) -> simple_longident id) fields
    | Pexp_function (params, _, Pfunction_body _) ->
        if
          List.for_all
            (function
              | { pparam_desc = Pparam_newtype _ } -> true
              | { pparam_desc = Pparam_val _ } -> false)
            params
        then function_without_value_parameters loc
    | Pexp_struct_item ({pstr_loc = loc;
                         pstr_desc = Pstr_eval _
                                   | Pstr_value _
                                   | Pstr_include _}, _) ->
        invalid_struct_item loc
    | _ -> ()
  in
  let extension_constructor self ec =
    super.extension_constructor self ec;
    match ec.pext_kind with
    | Pext_rebind id -> simple_longident id
    | _ -> ()
  in
  let class_expr self ce =
    super.class_expr self ce;
    let loc = ce.pcl_loc in
    match ce.pcl_desc with
    | Pcl_apply (_, []) -> no_args loc
    | Pcl_constr (id, _) -> simple_longident id
    | _ -> ()
  in
  let module_type self mty =
    super.module_type self mty;
    match mty.pmty_desc with
    | Pmty_alias id -> simple_longident id
    | _ -> ()
  in
  let open_description self opn =
    super.open_description self opn
  in
  let with_constraint self wc =
    super.with_constraint self wc;
    match wc with
    | Pwith_type (id, _)
    | Pwith_module (id, _) -> simple_longident id
    | _ -> ()
  in
  let module_expr self me =
    super.module_expr self me;
    match me.pmod_desc with
    | Pmod_ident id -> simple_longident id
    | _ -> ()
  in
  let structure_item self st =
    super.structure_item self st;
    let loc = st.pstr_loc in
    match st.pstr_desc with
    | Pstr_type (_, []) -> empty_type loc
    | Pstr_value (_, []) -> empty_let loc
    | _ -> ()
  in
  let signature_item self sg =
    super.signature_item self sg;
    let loc = sg.psig_loc in
    match sg.psig_desc with
    | Psig_type (_, []) -> empty_type loc
    | Psig_modtypesubst {pmtd_type=None; _ } ->
        module_type_substitution_missing_rhs loc
    | _ -> ()
  in
  let row_field self field =
    super.row_field self field;
    let loc = field.prf_loc in
    match field.prf_desc with
    | Rtag _ -> ()
    | Rinherit _ ->
      if field.prf_attributes = []
      then ()
      else err loc
          "In variant types, attaching attributes to inherited \
           subtypes is not allowed."
  in
  let object_field self field =
    super.object_field self field;
    let loc = field.pof_loc in
    match field.pof_desc with
    | Otag _ -> ()
    | Oinherit _ ->
      if field.pof_attributes = []
      then ()
      else err loc
          "In object types, attaching attributes to inherited \
           subtypes is not allowed."
  in
  let attribute self attr =
    (* The change to `self` here avoids registering attributes within attributes
       for the purposes of warning 53, while keeping all the other invariant
       checks for attribute payloads.  See comment on [current_phase] in
       [builtin_attributes.mli]. *)
    super.attribute { self with attribute = super.attribute } attr;
    Builtin_attributes.(register_attr Invariant_check attr.attr_name)
  in
  { super with
    type_declaration
  ; typ
  ; pat
  ; expr
  ; extension_constructor
  ; class_expr
  ; module_expr
  ; module_type
  ; open_description
  ; with_constraint
  ; structure_item
  ; signature_item
  ; row_field
  ; object_field
  ; attribute
  }

let structure st = iterator.structure iterator st
let signature sg = iterator.signature iterator sg

let check_loc_ghost meth v ~source_contents =
  let equal_modulo_loc =
    let no_locs =
      { Ast_mapper.default_mapper
        with location = (fun _ _ -> Location.none);
             attributes = (fun _ _ -> []);
        (* type z = (int [@foo]) create int at location "int" instead of
           "int [@foo]". I'd rather loosen the check than worsen the location
           for type errors. *)
      }
    in
    fun meth node1 node2 ->
      let norm1 = (meth no_locs) no_locs node1 in
      let norm2 = (meth no_locs) no_locs node2 in
      Stdlib.(=) norm1 norm2
  in
  let super = Ast_iterator.default_iterator in
  let depth = ref 0 in
  let limit_quadratic_complexity meth f =
    fun self v ->
      if !depth < 1000 then (
        depth := !depth + 1;
        (meth super) self v;
        depth := !depth -1 ;
        f v;
    )
  in
  let check ?print ?(wrap = Fun.id) meth parse ast1 (loc : Location.t) =
    let source_fragment =
      wrap (
          String.sub source_contents
            loc.loc_start.pos_cnum
            (loc.loc_end.pos_cnum - loc.loc_start.pos_cnum)
        )
    in
    let lexbuf = Lexing.from_string source_fragment in
    let should_be_loc_ghost, error_if_not =
      match parse lexbuf with
      | exception Parsing.Parse_error | exception _ ->
         true, "non-ghost location points to a non parsable range"
      | ast2 ->
         if equal_modulo_loc meth ast1 ast2
         then false, "ghost location should be non-ghost"
         else true, "non-ghost location points to a range of source \
                     code that contains the wrong ast"
    in
    if loc.loc_ghost <> should_be_loc_ghost
    then (
      Format.eprintf "@[<2>%a: %s%t@]@." Location.print_loc loc error_if_not
        (fun f ->
          match print with
          | None -> ()
          | Some print -> Format.fprintf f "@\n%a" print ast1)
    )
  in
  let self =
    { super with
      expr =
        limit_quadratic_complexity (fun s -> s.expr)
          (fun v ->
            check (fun s -> s.expr) Parse.expression v v.pexp_loc
              (* ~print:(fun f ty -> Printast.expression 0 f ty) *)
              (* Add parens because in 1 + 2, + gets assigned a non-ghost
                 location, but + without parens is not a valid expression. *)
              ~wrap:(fun s -> "( " ^ s ^ " )"))
    ; pat =
        limit_quadratic_complexity (fun s -> s.pat)
          (fun v -> check (fun s -> s.pat) Parse.pattern v v.ppat_loc )
    ; typ =
        limit_quadratic_complexity (fun s -> s.typ)
          (fun v ->
            check
              (* ~print:(fun f ty -> Printast.payload 0 f (PTyp ty)) *)
              (fun s -> s.typ) Parse.core_type v v.ptyp_loc )
    ; attribute = (fun self attr ->
      (* Doc comments would probably need some special case to check they are
         correctly placed. *)
      if attr.attr_name.txt = "ocaml.doc"
         || attr.attr_name.txt = "ocaml.text"
      then ()
      else super.attribute self attr)
    }
  in
  (meth self) self v
