Have `-Wformat' remain quiet for any procedure called `_' or `N_'.
[bpt/guile.git] / module / language / tree-il / analyze.scm
index 60a5bcd..c3ff9e2 100644 (file)
@@ -1,6 +1,6 @@
 ;;; TREE-IL -> GLIL compiler
 
-;; Copyright (C) 2001, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
+;; Copyright (C) 2001, 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
 
 ;;;; This library is free software; you can redistribute it and/or
 ;;;; modify it under the terms of the GNU Lesser General Public
@@ -22,7 +22,9 @@
   #:use-module (srfi srfi-1)
   #:use-module (srfi srfi-9)
   #:use-module (srfi srfi-11)
+  #:use-module (srfi srfi-26)
   #:use-module (ice-9 vlist)
+  #:use-module (ice-9 match)
   #:use-module (system base syntax)
   #:use-module (system base message)
   #:use-module (system vm program)
 
   ;; returns variables referenced in expr
   (define (analyze! x proc labels-in-proc tail? tail-call-args)
-    (define (step y) (analyze! y proc labels-in-proc #f #f))
+    (define (step y) (analyze! y proc '() #f #f))
     (define (step-tail y) (analyze! y proc labels-in-proc tail? #f))
     (define (step-tail-call y args) (analyze! y proc labels-in-proc #f
                                               (and tail? args)))
       (else '())))
   
   ;; allocation: sym -> {lambda -> address}
-  ;;             lambda -> (nlocs labels . free-locs)
+  ;;             lambda -> (labels . free-locs)
+  ;;             lambda-case -> (gensym . nlocs)
   (define allocation (make-hash-table))
   
   (define (allocate! x proc n)
@@ -867,25 +870,20 @@ accurate information is missing from a given `tree-il' element."
   ;; the name of the variable being defined; otherwise return #f.  This
   ;; assumes knowledge of the current implementation of `define-class' et al.
   (define (toplevel-define-arg args)
-    (and (pair? args) (pair? (cdr args)) (null? (cddr args))
-         (record-case (car args)
-           ((<const> exp)
-            (and (symbol? exp) exp))
-           (else #f))))
-
-  (record-case proc
-    ((<module-ref> mod public? name)
-     (and (equal? mod '(oop goops))
-          (not public?)
-          (eq? name 'toplevel-define!)
-          (toplevel-define-arg args)))
-    ((<toplevel-ref> name)
+    (match args
+      ((($ <const> _ (and (? symbol?) exp)) _)
+       exp)
+      (_ #f)))
+
+  (match proc
+    (($ <module-ref> _ '(oop goops) 'toplevel-define! #f)
+     (toplevel-define-arg args))
+    (($ <toplevel-ref> _ 'toplevel-define!)
      ;; This may be the result of expanding one of the GOOPS macros within
      ;; `oop/goops.scm'.
-     (and (eq? name 'toplevel-define!)
-          (eq? env (resolve-module '(oop goops)))
+     (and (eq? env (resolve-module '(oop goops)))
           (toplevel-define-arg args)))
-    (else #f)))
+    (_ #f)))
 
 (define unbound-variable-analysis
   ;; Report possibly unbound variables in the given tree.
@@ -1196,8 +1194,15 @@ accurate information is missing from a given `tree-il' element."
                              (false-if-exception
                               (module-ref env name))))
                       proc)))
-            (if (or (lambda? proc*) (procedure? proc*))
-                (validate-arity proc* application (lambda? proc*)))))
+            (cond ((lambda? proc*)
+                   (validate-arity proc* application #t))
+                  ((struct? proc*)
+                   ;; An applicable struct.
+                   (let ((p (struct-ref proc* 0)))
+                     (and (procedure? p)
+                          (validate-arity p application #f))))
+                  ((procedure? proc*)
+                   (validate-arity proc* application #f)))))
         toplevel-calls)))
 
    (make-arity-info vlist-null vlist-null vlist-null)))
@@ -1330,6 +1335,12 @@ accurate information is missing from a given `tree-il' element."
               ;; We don't have enough info to determine the exact number
               ;; of args, but we could determine a lower bound (TODO).
               (values 'any 'any))
+             ((#\h #\H)
+                        (let ((argc (if (memq #\: params) 2 1)))
+                          (loop (cdr chars) 'literal '()
+                                conditions end-group
+                                (+ argc min-count)
+                                (+ argc max-count))))
              (else      (loop (cdr chars) 'literal '()
                               conditions end-group
                               (+ 1 min-count) (+ 1 max-count)))))
@@ -1343,6 +1354,54 @@ accurate information is missing from a given `tree-il' element."
                               min-count max-count))))
           (else (error "computer bought the farm" state))))))
 
+(define (proc-ref? exp proc special-name env)
+  "Return #t when EXP designates procedure PROC in ENV.  As a last
+resort, return #t when EXP refers to the global variable SPECIAL-NAME."
+
+  (define special?
+    (cut eq? <> special-name))
+
+  (match exp
+    (($ <toplevel-ref> _ (? special?))
+     ;; Allow top-levels like: (define _ (cut gettext <> "my-domain")).
+     #t)
+    (($ <toplevel-ref> _ name)
+     (let ((var (module-variable env name)))
+       (and var (variable-bound? var)
+            (eq? (variable-ref var) proc))))
+    (($ <module-ref> _ _ (? special?))
+     #t)
+    (($ <module-ref> _ module name public?)
+     (let* ((mod (if public?
+                     (false-if-exception (resolve-interface module))
+                     (resolve-module module #:ensure #f)))
+            (var (and mod (module-variable mod name))))
+       (and var (variable-bound? var) (eq? (variable-ref var) proc))))
+    (($ <lexical-ref> _ (? special?))
+     #t)
+    (_ #f)))
+
+(define gettext? (cut proc-ref? <> gettext '_ <>))
+(define ngettext? (cut proc-ref? <> ngettext 'N_ <>))
+
+(define (const-fmt x env)
+  ;; Return the literal format string for X, or #f.
+  (match x
+    (($ <const> _ (? string? exp))
+     exp)
+    (($ <application> _ (? (cut gettext? <> env))
+        (($ <const> _ (? string? fmt))))
+     ;; Gettexted literals, like `(_ "foo")'.
+     fmt)
+    (($ <application> _ (? (cut ngettext? <> env))
+        (($ <const> _ (? string? fmt)) ($ <const> _ (? string?)) _ ..1))
+     ;; Plural gettextized literals, like `(N_ "singular" "plural" n)'.
+
+     ;; TODO: Check whether the singular and plural strings have the
+     ;; same format escapes.
+     fmt)
+    (_ #f)))
+
 (define format-analysis
   ;; Report arity mismatches in the given tree.
   (make-tree-analysis
@@ -1355,52 +1414,92 @@ accurate information is missing from a given `tree-il' element."
      (define (check-format-args args loc)
        (pmatch args
          ((,port ,fmt . ,rest)
-          (guard (const? fmt))
+          (guard (const-fmt fmt env))
           (if (and (const? port)
                    (not (boolean? (const-exp port))))
               (warning 'format loc 'wrong-port (const-exp port)))
-          (let ((fmt   (const-exp fmt))
+          (let ((fmt   (const-fmt fmt env))
                 (count (length rest)))
-            (if (string? fmt)
-                (catch &syntax-error
-                  (lambda ()
-                    (let-values (((min max)
-                                  (format-string-argument-count fmt)))
-                      (and min max
-                           (or (and (or (eq? min 'any) (>= count min))
-                                    (or (eq? max 'any) (<= count max)))
-                               (warning 'format loc 'wrong-format-arg-count
-                                        fmt min max count)))))
-                  (lambda (_ key)
-                    (warning 'format loc 'syntax-error key fmt)))
-                (warning 'format loc 'wrong-format-string fmt))))
+            (catch &syntax-error
+              (lambda ()
+                (let-values (((min max)
+                              (format-string-argument-count fmt)))
+                  (and min max
+                       (or (and (or (eq? min 'any) (>= count min))
+                                (or (eq? max 'any) (<= count max)))
+                           (warning 'format loc 'wrong-format-arg-count
+                                    fmt min max count)))))
+              (lambda (_ key)
+                (warning 'format loc 'syntax-error key fmt)))))
          ((,port ,fmt . ,rest)
-          ;; Warn on non-literal format strings, unless they refer to a
-          ;; lexical variable named "fmt".
-          (if (record-case fmt
-                ((<lexical-ref> name)
-                 (not (eq? name 'fmt)))
-                (else #t))
-              (warning 'format loc 'non-literal-format-string)))
+          (if (and (const? port)
+                   (not (boolean? (const-exp port))))
+              (warning 'format loc 'wrong-port (const-exp port)))
+
+          (match fmt
+            (($ <const> loc* (? (negate string?) fmt))
+             (warning 'format (or loc* loc) 'wrong-format-string fmt))
+
+            ;; Warn on non-literal format strings, unless they refer to
+            ;; a lexical variable named "fmt".
+            (($ <lexical-ref> _ fmt)
+             #t)
+            ((? (negate const?))
+             (warning 'format loc 'non-literal-format-string))))
          (else
           (warning 'format loc 'wrong-num-args (length args)))))
 
+     (define (check-simple-format-args args loc)
+       ;; Check the arguments to the `simple-format' procedure, which is
+       ;; less capable than that of (ice-9 format).
+
+       (define allowed-chars
+         '(#\A #\S #\a #\s #\~ #\%))
+
+       (define (format-chars fmt)
+         (let loop ((chars  (string->list fmt))
+                    (result '()))
+           (match chars
+             (()
+              (reverse result))
+             ((#\~ opt rest ...)
+              (loop rest (cons opt result)))
+             ((_ rest ...)
+              (loop rest result)))))
+
+       (match args
+         ((port ($ <const> _ (? string? fmt)) _ ...)
+          (let ((opts (format-chars fmt)))
+            (or (every (cut memq <> allowed-chars) opts)
+                (begin
+                  (warning 'format loc 'simple-format fmt
+                           (find (negate (cut memq <> allowed-chars)) opts))
+                  #f))))
+         ((port (= (cut const-fmt <> env) (? string? fmt)) args ...)
+          (check-simple-format-args `(,port ,(make-const loc fmt) ,args) loc))
+         (_ #t)))
+
      (define (resolve-toplevel name)
        (and (module? env)
             (false-if-exception (module-ref env name))))
 
-     (record-case x
-       ((<application> proc args src)
-        (let ((loc src))
-          (record-case proc
-            ((<toplevel-ref> name src)
-             (let ((proc (resolve-toplevel name)))
-               (and (or (eq? proc format)
-                        (eq? proc (@ (ice-9 format) format)))
-                    (check-format-args args (or src (find pair? locs))))))
-            (else #t)))
-        #t)
-       (else #t))
+     (match x
+       (($ <application> src ($ <toplevel-ref> _ name) args)
+        (let ((proc (resolve-toplevel name)))
+          (if (or (and (eq? proc (@ (guile) simple-format))
+                       (check-simple-format-args args
+                                                 (or src (find pair? locs))))
+                  (eq? proc (@ (ice-9 format) format)))
+              (check-format-args args (or src (find pair? locs))))))
+       (($ <application> src ($ <module-ref> _ '(ice-9 format) 'format) args)
+        (check-format-args args (or src (find pair? locs))))
+       (($ <application> src ($ <module-ref> _ '(guile)
+                                (or 'format 'simple-format))
+           args)
+        (and (check-simple-format-args args
+                                       (or src (find pair? locs)))
+             (check-format-args args (or src (find pair? locs)))))
+       (_ #t))
      #t)
 
    (lambda (x _ env locs)