diff --git a/src/kernel_internals/typing/cabs2cil.ml b/src/kernel_internals/typing/cabs2cil.ml
index f3b479e9c0745c243ba404dee8ce6e8acd4e4c8b..506bf706f271405e21544102503885ce36a9dff6 100644
--- a/src/kernel_internals/typing/cabs2cil.ml
+++ b/src/kernel_internals/typing/cabs2cil.ml
@@ -2704,51 +2704,92 @@ let rec combineTypes (what: combineWhat) (oldt: typ) (t: typ) : typ =
 
 let get_qualifiers t = Cil.filter_qualifier_attributes (Cil.typeAttrs t)
 
-let equal_qualifiers a1 a2 =
-  Cil_datatype.Attributes.equal
-    (Cil.filter_qualifier_attributes a1) (Cil.filter_qualifier_attributes a2)
+(* how type qualifiers must be checked *)
+type qualifier_check_context =
+  | Identical (* identical qualifiers. *)
+  | IdenticalToplevel (* ignore at toplevel, use Identical when going under a
+                         pointer. *)
+  | Covariant (* first type can have const-qualifications
+                 the second doesn't have. *)
+  | CovariantToplevel
+  (* accepts everything for current type, use Covariant when going under a
+     pointer. *)
+  | Contravariant (* second type can have const-qualifications
+                     the first doesn't have. *)
+  | ContravariantToplevel
+  (* accepts everything for current type, use Contravariant when going under
+     a pointer. *)
+
+let qualifier_context_fun_arg = function
+  | Identical | IdenticalToplevel -> IdenticalToplevel
+  | Covariant | CovariantToplevel -> ContravariantToplevel
+  | Contravariant | ContravariantToplevel -> CovariantToplevel
+
+let qualifier_context_fun_ret = function
+  | Identical | IdenticalToplevel -> IdenticalToplevel
+  | Covariant | CovariantToplevel -> CovariantToplevel
+  | Contravariant | ContravariantToplevel -> ContravariantToplevel
+
+let qualifier_context_ptr = function
+  | Identical | IdenticalToplevel -> Identical
+  | Covariant | CovariantToplevel -> Covariant
+  | Contravariant | ContravariantToplevel -> Contravariant
+
+let included_qualifiers ?(context=Identical) a1 a2 =
+  let a1 = Cil.filter_qualifier_attributes a1 in
+  let a2 = Cil.filter_qualifier_attributes a2 in
+  let a1 = Cil.dropAttribute "restrict" a1 in
+  let a2 = Cil.dropAttribute "restrict" a2 in
+  let a1_no_const = Cil.dropAttribute "const" a1 in
+  let a2_no_const = Cil.dropAttribute "const" a2 in
+  let is_equal = Cil_datatype.Attributes.equal a1 a2 in
+  if is_equal then true
+  else begin
+    match context with
+    | Identical -> false
+    | Covariant -> Cil_datatype.Attributes.equal a1_no_const a2
+    | Contravariant -> Cil_datatype.Attributes.equal a1 a2_no_const
+    | CovariantToplevel | ContravariantToplevel | IdenticalToplevel -> true
+  end
 
 (* precondition: t1 and t2 must be "compatible" as per combineTypes, i.e.
-   you must have called [combineTypes t1 t2] before calling this function.
-   When [relaxed] is true, qualifier differences are ignored; this is
-   an internal parameter used during recursive calls.
-   The qualifier compatibility algorithm is:
-   - by default, type qualifiers are ignored (e.g. for basic types);
-   - when entering a pointer type, stop ignoring type qualifiers;
-   - when entering a function type, resume ignoring type qualifiers. *)
-let rec have_compatible_qualifiers_deep ?(relaxed=false) t1 t2 =
+   you must have called [combineTypes t1 t2] before calling this function. *)
+let rec have_compatible_qualifiers_deep ?(context=Identical) t1 t2 =
   match unrollType t1, unrollType t2 with
   | TFun (tres1, Some args1, _, _), TFun (tres2, Some args2, _, _) ->
-    have_compatible_qualifiers_deep ~relaxed:true tres1 tres2 &&
+    have_compatible_qualifiers_deep
+      ~context:(qualifier_context_fun_ret context) tres1 tres2 &&
+    let context = qualifier_context_fun_arg context in
     List.for_all2 (fun (_, t1', a1) (_, t2', a2) ->
-        have_compatible_qualifiers_deep ~relaxed:true t1' t2' &&
-        equal_qualifiers a1 a2)
+        have_compatible_qualifiers_deep ~context t1' t2' &&
+        included_qualifiers ~context a1 a2)
       args1 args2
   | TPtr (t1', a1), TPtr (t2', a2)
   | TArray (t1', _, _, a1), TArray (t2', _, _, a2) ->
-    have_compatible_qualifiers_deep ~relaxed:false t1' t2' &&
-    (relaxed || equal_qualifiers a1 a2)
-  | _, _ -> relaxed || equal_qualifiers (Cil.typeAttrs t1) (Cil.typeAttrs t2)
+    (included_qualifiers ~context a1 a2) &&
+    let context = qualifier_context_ptr context in
+    have_compatible_qualifiers_deep ~context t1' t2'
+  | _, _ -> included_qualifiers ~context (Cil.typeAttrs t1) (Cil.typeAttrs t2)
 
-let compatibleTypes t1 t2 =
+let compatibleTypes ?context t1 t2 =
   try
     let r = combineTypes CombineOther t1 t2 in
     (* C99, 6.7.3 §9: "... to be compatible, both shall have the identically
        qualified version of a compatible type;" *)
-    if not (have_compatible_qualifiers_deep t1 t2) then
+    if not (have_compatible_qualifiers_deep ?context t1 t2) then
       raise (Cannot_combine "different qualifiers");
     (* Note: different non-qualifier attributes will be silently dropped. *)
     r
   with Cannot_combine _ as e ->
     raise e
 
-let areCompatibleTypes t1 t2 =
+let areCompatibleTypes ?context t1 t2 =
   try
-    ignore (compatibleTypes t1 t2); true
+    ignore (compatibleTypes ?context t1 t2); true
   with Cannot_combine _ -> false
 
 (* Specify whether the cast is from the source code *)
-let rec castTo ?(fromsource=false)
+let rec castTo ?context ?(fromsource=false)
     (ot : typ) (nt : typ) (e : exp) : (typ * exp ) =
   Kernel.debug ~dkey:Kernel.dkey_typing_cast "@[%t: castTo:%s %a->%a@\n@]"
     Cil.pp_thisloc (if fromsource then "(source)" else "")
@@ -2803,7 +2844,7 @@ let rec castTo ?(fromsource=false)
           "conversion between function types with \
            different number of arguments:@ %a@ and@ %a"
           Cil_printer.pp_typ ot Cil_printer.pp_typ nt;
-      if not (areCompatibleTypes ot nt) then
+      if not (areCompatibleTypes ?context ot nt) then
         Kernel.warning
           ~wkey:Kernel.wkey_incompatible_types_call
           ~current:true
@@ -2818,7 +2859,7 @@ let rec castTo ?(fromsource=false)
         | Lval lv ->  Cil.mkAddrOf ~loc:e.eloc lv
         | _ -> e (* function decay into pointer anyway *)
       in
-      castTo ~fromsource (TPtr (ot', [])) nt' clean_e
+      castTo ?context ~fromsource (TPtr (ot', [])) nt' clean_e
 
     (* accept converting a ptr to function to/from a ptr to void, even though
        not really accepted by the standard. gcc supports it. though
@@ -2834,10 +2875,10 @@ let rec castTo ?(fromsource=false)
        original type in the sources.
     *)
     | TPtr(TFun _,_), TPtr(TNamed(ti,nattr),pattr) ->
-      castTo
+      castTo ?context
         ~fromsource ot (TPtr (Cil.typeAddAttributes nattr ti.ttype, pattr)) e
     | TPtr(TNamed(ti,nattr),pattr), TPtr(TFun _,_) ->
-      castTo
+      castTo ?context
         ~fromsource (TPtr (Cil.typeAddAttributes nattr ti.ttype, pattr)) nt e
 
     (* No other conversion implying a pointer to function
@@ -2938,7 +2979,7 @@ let rec castTo ?(fromsource=false)
                   Cil_printer.pp_exp e
             in
             (* Continue casting *)
-            castTo ~fromsource:fromsource fstfield.ftype nt' e'
+            castTo ?context ~fromsource:fromsource fstfield.ftype nt' e'
           end
       end
     | _ ->
@@ -6603,7 +6644,9 @@ and doExp local_env
             in
             (add_reads ~ghost:local_env.is_ghost loc r c, e, t)
           in
-          let (texpected, a'') = castTo att at a' in
+          let (texpected, a'') =
+            castTo ~context:ContravariantToplevel att at a'
+          in
           (* A posteriori check that the argument type was compatible,
              to generate a warning otherwise;
              if a'' = a', no check needs to be done (no cast was introduced).
@@ -6631,11 +6674,7 @@ and doExp local_env
                  (Cil.isAnyCharPtrType texpected && Cil.isVoidPtrType att) ||
                  (* always allow null pointers *)
                  (Cil.isPointerType texpected && Ast_info.is_null_expr a') ||
-                 areCompatibleTypes texpected att ||
-                 (let texpected_no_qualif =
-                    Cil.typeRemoveAttributesDeep ["const"; "restrict"] texpected
-                  in
-                  areCompatibleTypes texpected_no_qualif att)
+                 areCompatibleTypes ~context:ContravariantToplevel att texpected
                in
                let ok =
                  if ok1 then true
@@ -10220,6 +10259,9 @@ let convFile (path, f) =
     globinitcalled = false;
   }
 
+(* export function without internal `relaxed' argument. *)
+let areCompatibleTypes t1 t2 = areCompatibleTypes t1 t2
+
 (*
 Local Variables:
 compile-command: "make -C ../../.."
diff --git a/tests/syntax/const_formals.i b/tests/syntax/const_formals.i
new file mode 100644
index 0000000000000000000000000000000000000000..ceba7f1c8ce2e5a0aea490d867a17bacaae1f22a
--- /dev/null
+++ b/tests/syntax/const_formals.i
@@ -0,0 +1,34 @@
+const char* s = "foo";
+char mutable_s[] = "bar";
+const char**p = &s;
+
+char f(const char* s) { return s[0]; }
+
+char g(const char* const* a) { return **a; }
+
+// we ignore restrict differences. GCC and Clang do that too
+void foo(int *restrict x, int* y);
+void foo(int *x, int* restrict y) { *x++; *y--; }
+
+// play a little bit with function pointers.
+int incr_get(int* x) { *x++; return *x; }
+
+int get(const int*x) { return *x; }
+
+int apply_const(int* x, int (*g)(const int*y)) {
+  return g(x);
+}
+
+int apply_non_const (int* x, int(*g)(int*y)) {
+  return g(x);
+}
+
+int main() {
+  int x = 1;
+  apply_const(&x,incr_get); // should warn
+  apply_non_const(&x,incr_get); // ok
+  apply_const(&x,get); // ok
+  apply_non_const(&x,get); // ok
+  char c = f(mutable_s); // implicit conversion should not raise warning
+  char d = g(p); // implicit conversion should not raise warning
+}
diff --git a/tests/syntax/oracle/const_formals.res.oracle b/tests/syntax/oracle/const_formals.res.oracle
new file mode 100644
index 0000000000000000000000000000000000000000..b3375fd904feb5b2d08097ab2785ec2cfda54cb3
--- /dev/null
+++ b/tests/syntax/oracle/const_formals.res.oracle
@@ -0,0 +1,88 @@
+[kernel] Parsing tests/syntax/const_formals.i (no preprocessing)
+[kernel:typing:incompatible-types-call] tests/syntax/const_formals.i:28: Warning: 
+  implicit conversion between incompatible function types:
+  int (*)(int *x)
+  and
+  int (*)(int const *y)
+[kernel:typing:incompatible-types-call] tests/syntax/const_formals.i:28: Warning: 
+  expected 'int (*)(int const *y)' but got argument of type 'int (*)(int *x)': & incr_get
+/* Generated by Frama-C */
+char const *s = "foo";
+char mutable_s[4] = {(char)'b', (char)'a', (char)'r', (char)'\000'};
+char const **p = & s;
+char f(char const *s_0)
+{
+  char __retres;
+  __retres = *(s_0 + 0);
+  return __retres;
+}
+
+char g(char const * const *a)
+{
+  char __retres;
+  __retres = *(*a);
+  return __retres;
+}
+
+void foo(int *x, int * __restrict y);
+
+void foo(int *x, int * __restrict y)
+{
+  int *tmp;
+  int *tmp_0;
+  tmp = x;
+  x ++;
+  ;
+  tmp_0 = y;
+  y --;
+  ;
+  return;
+}
+
+int incr_get(int *x)
+{
+  int __retres;
+  int *tmp;
+  tmp = x;
+  x ++;
+  ;
+  __retres = *x;
+  return __retres;
+}
+
+int get(int const *x)
+{
+  int __retres;
+  __retres = *x;
+  return __retres;
+}
+
+int apply_const(int *x, int (*g_0)(int const *y))
+{
+  int tmp;
+  tmp = (*g_0)((int const *)x);
+  return tmp;
+}
+
+int apply_non_const(int *x, int (*g_0)(int *y))
+{
+  int tmp;
+  tmp = (*g_0)(x);
+  return tmp;
+}
+
+int main(void)
+{
+  int __retres;
+  int x = 1;
+  apply_const(& x,(int (*)(int const *y))(& incr_get));
+  apply_non_const(& x,& incr_get);
+  apply_const(& x,& get);
+  apply_non_const(& x,(int (*)(int *y))(& get));
+  char c = f((char const *)(mutable_s));
+  char d = g((char const * const *)p);
+  __retres = 0;
+  return __retres;
+}
+
+