m3tk/src/warn/M3CChkUses.m3


*************************************************************************
                      Copyright (C) Olivetti 1989                        
                          All Rights reserved                            
                                                                         
 Use and copy of this software and preparation of derivative works based 
 upon this software are permitted to any person, provided this same      
 copyright notice and the following Olivetti warranty disclaimer are      
 included in any copy of the software or any modification thereof or     
 derivative work therefrom made by any person.                           
                                                                         
 This software is made available AS IS and Olivetti disclaims all        
 warranties with respect to this software, whether expressed or implied  
 under any law, including all implied warranties of merchantibility and  
 fitness for any purpose. In no event shall Olivetti be liable for any   
 damages whatsoever resulting from loss of use, data or profits or       
 otherwise arising out of or in connection with the use or performance   
 of this software.                                                       
*************************************************************************

 Copyright (C) 1991, Digital Equipment Corporation           
 All rights reserved.                                        
 See the file COPYRIGHT for a full description.              

MODULE M3CChkUses;

IMPORT IntRefTbl, RefList, Word, Text;
IMPORT AST, M3AST_AS, M3AST_SM;
IMPORT ASTWalk, M3CPragma;

IMPORT M3AST_LX_F, M3AST_AS_F, M3AST_SM_F, M3AST_TM_F;

IMPORT SeqM3AST_AS_DECL_REVL;
IMPORT M3Error;

CONST
  DefIdTableSize = 256;

TYPE
  DefId = OBJECT
    used := FALSE; saysUnused := FALSE;
    defId: M3AST_AS.DEF_ID;
  END;

  Import = DefId OBJECT
    usedId: M3AST_AS.USED_ID;
  END;

  DefIdList = REF RefList.T; (* OF DefId *)

REVEAL
  Handle = ASTWalk.Closure BRANDED OBJECT
    interface: BOOLEAN;
    pragmas: M3CPragma.Store;
    ignoreHeaderOrFrom, ignoreFormals, inImported := FALSE;
    ignoreFORvars := FALSE; saysUnused := FALSE;
    table: IntRefTbl.T;
  OVERRIDES
    callback := Node;
  END;

PROCEDURE AddDefId(
    h: Handle;
    defId: M3AST_SM.DEF_ID_UNSET;
    usedId: M3AST_AS.USED_ID := NIL): DefId
    RAISES {}=
  VAR
    d: DefId;
    ra: REFANY;
    l: DefIdList;
  BEGIN
    IF usedId = NIL THEN
      d := NEW(DefId, defId := defId);
    ELSE
      d := NEW(Import, defId := defId, usedId := usedId);
    END;
    (* Assert: not in the table already *)
    IF h.table.get(defId.lx_srcpos, ra) THEN
      (* lx_srcpos clash *)
      l := NARROW(ra, DefIdList);
    ELSE
      l := NEW(DefIdList);
      EVAL h.table.put(defId.lx_srcpos, l);
    END;
    l^ := RefList.Cons(d, l^);
    RETURN d;
  END AddDefId;

PROCEDURE Lookup(
    h: Handle;
    defId: M3AST_AS.DEF_ID;
    VAR (*out*) d: DefId)
    : BOOLEAN
    RAISES {}=
  VAR
    ra: REFANY;
    l: DefIdList;
    dl: RefList.T;
  BEGIN
    (* cant hash references with a copying collector, so hash on
    source position and then use straight comparisions *)
    IF h.table.get(defId.lx_srcpos, ra) THEN
      l := NARROW(ra, DefIdList);
      dl := l^;
      WHILE dl # NIL DO
        d := dl.head;
        IF d.defId = defId THEN RETURN TRUE;
	ELSE
      	  dl := dl.tail;
        END;
      END; (* while *)
    END;
    RETURN FALSE
  END Lookup;

PROCEDURE Node(h: Handle; n: AST.NODE; vm: ASTWalk.VisitMode) RAISES {}=
  VAR
    usedId: M3AST_AS.USED_ID;
    d: DefId;
  BEGIN
    IF vm = ASTWalk.VisitMode.Entry THEN
      IF n.IsA_USED_ID(usedId) THEN
        IF h.ignoreHeaderOrFrom THEN
          (* We are ignoring a unit header (contains Interface_id or Module_id
           and possibly an exports list) or the interface in an unqualified
           import (i.e. FROM interface IMPORT). If we are ignoring a FROM
           interface we set 'ignore' to FALSE so we don't ignore the imported
           ids which follow *)
          IF h.inImported THEN h.ignoreHeaderOrFrom := FALSE END;
          RETURN;
        END;
        VAR
          defId := usedId.sm_def;
        BEGIN
          TYPECASE defId OF
          | NULL, M3AST_AS.Module_id, M3AST_AS.Enum_id,
            M3AST_AS.Field_id, M3AST_AS.METHOD_OVERRIDE_ID =>
              RETURN;
          ELSE
          END;
          IF Lookup(h, defId, d) THEN
            (* This name has already been entered *)
          ELSE
            (* First encounter; put it in the table. If we are dealing with
             an import we must pass the 'usedId' to 'AddDefId' *)
            IF NOT h.inImported THEN usedId := NIL END;
            d := AddDefId(h, defId, usedId);
          END;
          IF NOT h.inImported THEN
            d.used := TRUE;
          END;
        END;
        RETURN;
      END;

      TYPECASE n OF
      | M3AST_AS.UNIT =>
          h.ignoreHeaderOrFrom := TRUE;
      | M3AST_AS.IMPORTED(imported) =>
          h.ignoreHeaderOrFrom := ISTYPE(imported, M3AST_AS.From_import);
          h.inImported := TRUE;
      | M3AST_AS.Import_item(ii) =>
          h.ignoreHeaderOrFrom := ii.as_id # NIL;
      | M3AST_AS.Block =>
          h.ignoreHeaderOrFrom := FALSE;
          h.inImported := FALSE;
      | M3AST_AS.Procedure_type(procType) =>
          TYPECASE procType.sm_def_id OF
          | NULL =>
              h.ignoreFormals := TRUE;
          | M3AST_AS.Proc_id =>
              h.ignoreFormals := FALSE;
          ELSE
              h.ignoreFormals := TRUE;
          END;
      | M3AST_AS.Var_decl_s,
        M3AST_AS.Formal_param =>
          IF UnusedPragma(h, n) THEN h.saysUnused := TRUE END;

      | M3AST_AS.Module_id, M3AST_AS.Enum_id,
        M3AST_AS.Field_id, M3AST_AS.METHOD_OVERRIDE_ID =>
          (* Don't want to take the 'DEF_ID' arm; we don't track these ids *)
      | M3AST_AS.DEF_ID(defId) =>
          TYPECASE n OF
          | M3AST_AS.For_id =>
              IF h.ignoreFORvars THEN RETURN END;
	      IF UnusedPragma(h, n) THEN h.saysUnused := TRUE END;
          ELSE (* drop through *)
          END;
          IF h.ignoreHeaderOrFrom OR h.interface OR
              h.ignoreFormals AND ISTYPE(defId, M3AST_AS.FORMAL_ID) THEN
            RETURN
          END;
          TYPECASE defId OF
          | M3AST_AS.Proc_id(procId) =>
              (* Exported proc ids are not considered to be unused *)
              IF procId.vREDEF_ID.sm_int_def # NIL THEN RETURN END;
          ELSE
          END;
          IF NOT Lookup(h, defId, d) THEN
	    d := AddDefId(h, defId);
          END;
	  d.saysUnused := h.saysUnused;
     ELSE
     END; (* typecase *)
   ELSE (* vm = ASTWalk.VisitMode.Exit *)
     TYPECASE n OF
     | M3AST_AS.Var_decl_s, M3AST_AS.Formal_param => h.saysUnused := FALSE
     ELSE
     END;
   END
  END Node;

PROCEDURE UnusedPragma(h: Handle; n: AST.NODE):BOOLEAN RAISES {}=
  VAR
    iter := M3CPragma.BeforeNode(h.pragmas, n);
    pragma: M3CPragma.T;
    args: Text.T;
  BEGIN
    WHILE M3CPragma.Next(iter, pragma) AND
        M3CPragma.FollowingNode(pragma) = n DO
      IF M3CPragma.Match(pragma, "UNUSED", args) THEN
        RETURN TRUE;
      END;
    END;
    RETURN FALSE;
  END UnusedPragma;

PROCEDURE NewHandle(cu: M3AST_AS.Compilation_Unit;
  ignoreFORvars := FALSE): Handle RAISES {}=
  BEGIN
    RETURN NEW(Handle, interface := ISTYPE(cu.as_root, M3AST_AS.Interface),
        pragmas := cu.lx_pragmas,
        table := NEW(IntRefTbl.Default).init(DefIdTableSize),
	ignoreFORvars := ignoreFORvars);
  END NewHandle;

PROCEDURE Unused(d: DefId; defId: M3AST_AS.DEF_ID) RAISES {}=
  VAR
    errNode: M3Error.ERROR_NODE;
  BEGIN
    TYPECASE d OF
    | Import(import) =>
        TYPECASE defId OF
        | M3AST_AS.Interface_id(intId) =>
            VAR
              iter := SeqM3AST_AS_DECL_REVL.NewIter(
                  NARROW(intId.sm_spec, M3AST_AS.UNIT_WITH_BODY).as_block.as_decl_s);
              d: M3AST_AS.DECL_REVL;
            BEGIN
              WHILE SeqM3AST_AS_DECL_REVL.Next(iter, d) DO
                IF ISTYPE(d, M3AST_AS.Revelation_s) THEN RETURN END;
              END;
            END;
        ELSE
        END;
        errNode := import.usedId;
    ELSE
      errNode := defId;
    END;
    M3Error.WarnWithId(errNode, "\'%s\' is not used", defId.lx_symrep);
  END Unused;

PROCEDURE CloseHandle(h: Handle) RAISES {}=
  VAR
    i := h.table.iterate();
    key: Word.T;  value: REFANY;
    d: DefId;
    dl: RefList.T;
  BEGIN
    WHILE i.next(key, value) DO
      dl := NARROW(value, DefIdList)^;
      WHILE dl # NIL DO
        d := dl.head;
	IF d.used THEN
	  IF d.saysUnused THEN
            M3Error.WarnWithId(d.defId, "\'%s\' is NOT unused!",
	        d.defId.lx_symrep);
          END;
        ELSE IF NOT d.saysUnused THEN Unused(d, d.defId) END;
        END;
	dl := dl.tail;
      END;
    END;
  END CloseHandle;

BEGIN

END M3CChkUses.