Module:    DFMC-Typist
Author:    Steve Rowley
Synopsis:  Inference of the types in the typist.
Copyright:    Original Code is Copyright (c) 1995-2004 Functional Objects, Inc.
              All rights reserved.
License:      Functional Objects Library Public License Version 1.0
Dual-license: GNU Lesser General Public License
Warranty:     Distributed WITHOUT WARRANTY OF ANY KIND

///
/// Minimal static type inference: recurse until you ground out on data.
///

///
/// A few tools.
///

// *** Dependency: on the return, or last instruction?
define function type-estimate-body(body-first :: <computation>, 
                                   cache :: <type-cache>,
                                   #key before) => (te :: <type-estimate>)
  // Type the computations in a body (i.e., sequence of DFM instructions).
  // Goes up to the next ender, or before: if supplied.  The purpose of the
  // latter is to find an <end-exit-block>, which is not an <end>.
  walk-computations(rcurry(type-estimate-in-cache, cache), body-first, before);
  type-estimate-in-cache(case
                           before    => previous-computation(before);  // <end-exit-block>
                           otherwise => final-computation(body-first); // has an <end>
                         end,
                         cache)
end;

///
/// Machine to figure out the return value type(s) from a function type.
///

define generic function-valtype (fntype, cache :: <type-cache>) 
 // Figure out values type for this function type.
 => (valtype :: <type-estimate>);

define method function-valtype (fntype :: <type-estimate-limited-function>, 
                                cache :: <type-cache>)
   => (valtype :: <type-estimate>);
  // Easy to extract values type from a limited function
  type-estimate-values(fntype)
end;

define method function-valtype (fntype :: <type-estimate-limited-instance>, 
                                cache  :: <type-cache>)
   => (valtype :: <type-estimate>);
  // Coerce singleton to limited function & try again.
  let fn = type-estimate-singleton(fntype);
  if (instance?(fn, <&callable-object>))
    // It's guaranteed to be some kind of function
    let (sig, cl, body) =
      select (fn by instance?)
        <&lambda>    => values(^function-signature(fn), 
                               &object-class(fn), 
                               // body(fn)
                               #f);
        <&code>      => values(^function-signature(function(fn)), 
                               &object-class(fn), 
                               // body(function(fn))
                               #f);
        <&function>  => values(^function-signature(fn), 
                               &object-class(fn), 
                               #f);
        <&primitive> => values(primitive-signature(fn), 
                               &object-class(fn), 
                               #f);
      end;
    function-valtype(type-estimate-function-from-signature(sig, cl, cache,
                                                           body: body),
                     cache)
  else
    // It's not a callable thing; you'll never return.
    make(<type-estimate-bottom>)
  end
end;

define method function-valtype (fntype :: <type-estimate-union>, 
                                cache :: <type-cache>)
   => (valtype :: <type-estimate>);
  // Unions of function objects get processed recursively.
  make(<type-estimate-union>, 
       unionees: map(rcurry(function-valtype, cache), 
                     type-estimate-unionees(fntype)))
end;

define method function-valtype (fntype :: <object>, cache :: <type-cache>)
   => (valtype :: <type-estimate>);
  // Anything else just punts to the general case.  E.g., class <function>
  // goes here.  We could try to check for disjointness from <callable-object>, 
  // but that happense in the rest of the compiler 
  // (see optimization/dispatch.dylan).
  make(<type-estimate-values>)
end;

///
/// Manipulating values and unions of values.
///

define generic single-value?(vtype) => (single? :: <boolean>);

define method single-value?(vtype :: <type-estimate-values>)
    => (single? :: <boolean>)
  // Is this really only 1 return value?
  size(type-estimate-fixed-values(vtype)) = 1 &
       type-estimate-rest-values(vtype) == #f
end;

define method single-value?(vtype :: <type-estimate-union>)
 => (single? :: <boolean>)
  // Is this a union of single-value types?
  every?(single-value?, type-estimate-unionees(vtype))
end;

define generic first-value(vtype) => (first :: <type-estimate>);

define method first-value(vtype :: <type-estimate-values>) 
 => (val :: <type-estimate>)
  // First value of a multiple value type.
  case
    size(type-estimate-fixed-values(vtype)) ~== 0 
      => type-estimate-fixed-values(vtype)[0]; // First fixed value
    type-estimate-rest-values(vtype) 
      => type-estimate-rest-values(vtype);     // No fixed value; use rest
    otherwise
      => make(<type-estimate-limited-instance>, singleton: &false)
  end
end;

define method first-value(vtype :: <type-estimate-union>) 
 => (val :: <type-estimate>)
  // Union of first values of components.
  make(<type-estimate-union>, 
       unionees: map-as(<unionee-sequence>,
                        first-value, 
                        type-estimate-unionees(vtype)))
end;

///
/// How to type-estimate a call.
///

define inline function type-estimate-call-stupidly-from-fn
       (call :: <call>, fn, cache :: <type-cache>)
    => (te :: false-or(<type-estimate>))
  // Type of function-object.                  
  let fntype  = type-estimate-in-cache(fn, cache); 
  function-valtype(fntype, cache); // Values type or union thereof.
end function;

define method type-estimate-call-from-site(call :: <function-call>, 
                                           cache :: <type-cache>)
    => (te :: false-or(<type-estimate>))
  let (constant?, fn)  = constant-value?(call.function);
  if (instance?(fn, <&generic-function>))
    select (fn)
      dylan-value(#"make") =>
	let (c?, arg-type)
	  = constant-value?(first(arguments(call)));
	let type
	  = if (c? & ^instance?(arg-type, dylan-value(#"<type>")))
	      arg-type
	    else
	      dylan-value(#"<object>")
	    end if;
	make(<type-estimate-values>, 
	     fixed: vector(as(<type-estimate>, type)));
      dylan-value(#"element"), dylan-value(#"element-no-bounds-check") =>
	let collection-te = type-estimate(first(arguments(call)));
	if (instance?(collection-te, <type-estimate-limited-collection>))
	  make(<type-estimate-values>, 
	       fixed: vector(as(<type-estimate>, type-estimate-of(collection-te))))  
	else 
	  type-estimate-call-stupidly-from-fn(call, function(call), cache)
	end if;
      dylan-value(#"element-setter"), dylan-value(#"element-no-bounds-check-setter") =>
	let collection-te = type-estimate(second(arguments(call)));
	if (instance?(collection-te, <type-estimate-limited-collection>))
	  make(<type-estimate-values>, 
	       fixed: vector(as(<type-estimate>, type-estimate-of(collection-te))))  
	else 
	  type-estimate-call-stupidly-from-fn(call, function(call), cache)
	end if;
      otherwise =>
	type-estimate-call-stupidly-from-fn(call, function(call), cache)
    end select;
  else
    type-estimate-call-stupidly-from-fn(call, function(call), cache)
  end if;
end method;

define method ^make-return-class-from-signature
    (fn :: <&method>) => (res :: false-or(<&class>))
  let sig   = ^function-signature(fn);
  let rtype = first(^signature-required(sig));
  let vtype = first(^signature-values(sig));
  let class = select (rtype by instance?) 
		<&singleton> => ^singleton-object(rtype);
		<&subclass>  => ^subclass-class(rtype);
		otherwise    => #f;
	      end select;
  if (class & ~^subtype?(vtype, class))
    class
  else
    vtype
  end if
end method;

define method ^make-method? (fn :: <&method>) => (well? :: <boolean>)
  let binding = model-variable-binding(fn);
  binding & binding == dylan-binding(#"make")
end method;

define method ^make-method? (fn) => (well? :: <boolean>)
  #f
end method;

define method ^element-method? (fn :: <&method>) => (well? :: <boolean>)
  let binding = model-variable-binding(fn);
  binding 
    & (binding == dylan-binding(#"element")
	 | binding == dylan-binding(#"element-no-bounds-check"))
end method;

define method ^element-setter-method? (fn :: <&method>) => (well? :: <boolean>)
  let binding = model-variable-binding(fn);
  binding 
    & (binding == dylan-binding(#"element-setter")
	 | binding == dylan-binding(#"element-no-bounds-check-setter"))
end method;

define method type-estimate-call-from-site(call :: <method-call>, 
                                           cache :: <type-cache>)
    => (te :: false-or(<type-estimate>))
  let (constant?, fn)  = constant-value?(call.function);
  if (instance?(fn, <&method>))
    if (^make-method?(fn))
      let sig-class      
	= ^make-return-class-from-signature(fn);
      let (c?, arg-type)
	= constant-value?(first(arguments(call)));
      let type 
	= if (c? & ^subtype?(arg-type, sig-class))
	    arg-type
	  else
	    sig-class
	  end if;
      make(<type-estimate-values>, 
	   fixed: vector(as(<type-estimate>, type)))  
    elseif (^element-method?(fn))
      let collection-te = type-estimate(first(arguments(call)));
      if (instance?(collection-te, <type-estimate-limited-collection>))
	make(<type-estimate-values>, 
	     fixed: vector(as(<type-estimate>, type-estimate-of(collection-te))))  
      else 
	next-method()
      end if
    elseif (^element-setter-method?(fn))
      let collection-te = type-estimate(second(arguments(call)));
      if (instance?(collection-te, <type-estimate-limited-collection>))
	make(<type-estimate-values>, 
	     fixed: vector(as(<type-estimate>, type-estimate-of(collection-te))))  
      else 
	next-method()
      end if
    else 
      next-method()
    end if;
  else 
    next-method()
  end if;
end method;

define method type-estimate-call-from-site(call :: <primitive-call>, 
                                           cache :: <type-cache>)
    => (te :: false-or(<type-estimate>))
  let fn = primitive(call);
  block (return)
    // special cases
    when (fn == dylan-value(#"primitive-object-allocate-filled"))
      // type returned is actually contained in second argument
      let (c?, wrapper) = constant-value?(second(arguments(call)));
      when (c?)
        let iclass = ^mm-wrapper-implementation-class(wrapper);
        let class  = ^iclass-class(iclass);
        return(make(<type-estimate-values>, 
                    fixed: vector(as(<type-estimate>, class))))
      end when
    end when;
    type-estimate-call-stupidly-from-fn(call, fn, cache)
  end block;
end method;

// *** Dependency: on return value.
define function type-estimate-call-stupidly(call :: <call>, cache :: <type-cache>)
    => (te :: <type-estimate>)
  if (temporary(call))
    // Just look at the values declaration in the thing being called.
    let valtype = type-estimate-call-from-site(call, cache);
    if (multiple-values?(temporary(call)))
      // Takes multiple values.
      valtype
    else
      first-value(valtype)
    end
  else
    // call value not used -- no one cares what the result type is
    make(<type-estimate-values>)
  end
end;

///
/// Coercing a <&signature> to a <type-estimate-limited-function>.
///

/// TODO: GET THIS TO WORK WITH LIMITED VECTORS
define constant <&types>         = <simple-object-vector>;
//   = limited(<vector>, of: <&type>);
define constant <type-estimates> = <simple-object-vector>;
//   = limited(<vector>, of: <type-estimate>);

define function type-estimate-function-from-signature 
    (sig :: <&signature>, class :: <&class>, cache :: <type-cache>, #key body)
    => (te :: <type-estimate>)
  // Construct a limited function type-estimate based on the model signature
  local  method lift (x :: <&type>) => (te :: <type-estimate>)
           as(<type-estimate>, x)
         end, 
         method lift-sequence 
                (x :: <&types>, number-required :: <integer>)
             => (te* :: <type-estimates>)
	   let requireds :: <simple-object-vector>
	     = make(<type-estimates>, size: number-required);
	   for (e in x, i :: <integer> from 0 below number-required)
	     requireds[i] := lift(e)
	   end for;
	   requireds
         end;
  make(<type-estimate-limited-function>,
       class:     class,
       requireds: lift-sequence(as(<&types>, ^signature-required(sig)), 
                                ^signature-number-required(sig)),
       rest?:     ^signature-rest?(sig),
       // ^signature-keys? = #t iff accepts keys
       // ^signature-keys is nonempty iff there are local keys.
       // (remember #key #all-keys takes keys but none locally)
       // *** Redundancy in <signature> among keys, keys?, and all-keys? ?
       keys:      when (~empty?(^signature-keys(sig)))
                    let tbl = make(<object-table>);
                    for (key in ^signature-keys(sig))
                      // *** NB: key types not recorded in signatures, for
                      //     reasons that are obscure to me!
                      tbl[key] := as(<type-estimate>, dylan-value(#"<object>"));
                    end;
                    tbl
                  end,
       all-keys?: ^signature-all-keys?(sig),
       vals:      if (body)
                    // Prefer body to values in signature
                    type-estimate-body(body, cache)
                  else
                     make(<type-estimate-values>, 
                          // Could also do type-estimate-body, 
                          // if function body is available (e.g., <&lambda>).
                          fixed: lift-sequence(^signature-values(sig), 
                                               ^signature-number-values(sig)),
                          rest:  when (^signature-rest-value(sig))
                                   lift(^signature-rest-value(sig))
                                 end)
                  end)
end;

define function type-estimate-datum (obj) => (te :: <type-estimate>)
  // Make a singleton out of obj.
  make(<type-estimate-limited-instance>, singleton: obj)
end;

define function lift-model-named(name :: <symbol>) => (te :: <type-estimate>)
  // Evaluate name in Dylan, lift resulting model type as type-estimate.
  as(<type-estimate>, dylan-value(name))
end;

///
/// The type inference rule compiler.
///

///
/// Rule syntax: (nonterminals in UPPER-CASE, {... | ...} for alternatives)
///
/// RULE-FORM  ::= define type-inference-rules RULE-GROUP TYPE-RULES end;
/// RULE-GROUP ::= NAME
/// TYPE-RULES ::= TYPE-RULE; ...  (ok, actually 0 or more)
//
/// TYPE-RULE  ::= LHS CONNECTIVE RHS
///
/// CONNECTIVE ::= == | <- | <-*
///
/// LHS        ::= OBJ :: DFM-TYPE
/// OBJ        ::= NAME
/// DFM-TYPE   ::= NAME
///
/// RHS        ::= EXPRESSION | EXPRESSION, RHS
///
/// Rule semantics:
///
/// * On the LHS: 
///
///   - OBJ is a symbol, which will go into the arglist of a method.  The rest 
///     of the type-rule is in its scope.  It is the object whose type is 
///     being inferred.
///
///   - DFM-TYPE is a type of some kind of object in the DFM, i.e., a subtype
///     of <dfm-ref>.
///
///   - The LHS advertises a way to compute the type of OBJ, given the knowledge
///     that its representation in the DFM is of type DFM-TYPE.  E.g.,
///
///           pcall :: <primitive-call> == ...rhs involving pcall...
///
///     purports to tell you how to compute the type of a <primitive-call> 
///     computation, to be referred to as pcall in the rhs.
///
/// * The LHS is the trigger of the rule, i.e., the thing to be matched before
///   the rule can fire.  If the LHS is OBJ :: DFM-TYPE, then the rule defines 
///   a method with signature:
///
///         type-estimate-infer(OBJ :: DFM-TYPE, cache :: <type-cache>).
///
/// * There are two types of connectives between the LHS and RHS:
///
///   - A rule of the form:
///
///       OBJ :: DFM-TYPE == ...rhs...
///
///     defines a "basis rule" which calculates directly the type of OBJ.  The 
///     RHS should return a <type-estimate> which will be union'd with what's 
///     already known in the cache about OBJ.
///
///     To keep the justification semantics straight, the RHS really should be
///     a single expression that just constructes a <type-estimate>, and which
///     does not call type-estimate &c in the process.
///
///     E.g., the type of an <&object> is some class <stype>:
///
///       x :: <&object> == make(<type-estimate-class>, class: &object-class(x))
///
///   - A rule of the form: 
///
///       OBJ :: DFM-TYPE <- ...rhs...
///
///     defines an "induction rule" in which the typist recurses.  The RHS
///     should be a new DFM object, on which type-estimate-in-cache(_, _)
///     will be called recursively to produce the type of OBJ.  Thus OBJ defers
///     to some other object to determine its own type.
///
///     The RHS can be a ,-separated sequence of objects, in which case the
///     type generated is the union of the types of each of them.
///
///     E.g., the type of an <method-reference> is the type of the object
///     to which it refers:
///
///         obj-ref :: <method-reference> <- value(obj-ref);
///    
///     The other kind of induction rule uses the connective <-*.  In this case,
///     the last RHS object is assumed to be a list.  Kind of like apply.
///
///     E.g., the type of a <merge> is the union of the sources:
///   
///         merge :: <merge> <-* sources(merge);
///  
///   In either case, the RHS can refer to the variable cache, which contains
///   the current state of the mapping from objects to types.
///

// For desperate debugging mode.
// define thread variable *step-depth*      :: <integer> = -1;
define thread variable *tracing-infer?*  :: <boolean> = #f; // Show type inference 
define thread variable *stepping-infer?* :: <boolean> = #f; // Show + single-step 

define macro with-infer-stepping 
  { with-infer-stepping (?dfm-type:name) ?forms:body end }
  // => { with-infer-stepping-internal(?dfm-type, method () ?forms end) }
  => { ?forms }
end;

/*
define function with-infer-stepping-internal(dfm-type, body-fn :: <function>)
   => (te :: <type-estimate>)
  // Trace the inference rules, waiting for a character at each rule entry.
  if (*stepping-infer?* | *tracing-infer?*)
    local method step-indent () => ()
            format-out("\n"); 
            for (i from 0 below *step-depth*)
              write-element(*standard-output*, '|')
            end
          end,
          method step-infer-in (dfm-type) => ()
            step-indent(); 
            format-out("%= :: %=", *current-lhs*, dfm-type); 
            when (*stepping-infer?*)
              read(*standard-input*, 1)
            end
          end,
          method step-infer-out (answer) => ()
            step-indent(); 
            format-out("answer: %=", answer)
          end;
    dynamic-bind (*step-depth* = *step-depth* + 1)
      step-infer-in(dfm-type);
      let answer = body-fn();
      step-infer-out(answer);
      answer
    end
  else
    body-fn()
  end
end;
*/

define inline function type-union-with-sources
    (start-type :: <type-estimate>, sources :: <sequence>, cache :: <type-cache>)
    => (te :: <type-estimate>)
  // Start from start-type, and union in types of all the other sources.
  // Good for combining <merge> sources, variable assignments, etc.
  reduce(method (so-far, next-rhs)
           *current-rhs* := add!(*current-rhs*, next-rhs);
           type-estimate-union(so-far, type-estimate-in-cache(next-rhs, cache))
         end,
         start-type,
         sources)
end;

define macro type-infer-rhs
  // Either execute rhs code (basis rule) or recurse (induction rule).
  { type-infer-rhs(ET, ?rhs:expression) }               // Basis rule.
    => { ?rhs }

  { type-infer-rhs(LT, ?rhs:expression) }               // Induction rule (1).
    => { let the-rhs = ?rhs;
         *current-rhs* := add!(*current-rhs*, the-rhs);
         type-estimate-in-cache(the-rhs, ?=cache) }

  { type-infer-rhs(LT, ?rhs1:expression, ?rhs-more:*) } // Induction rule (2).
    => { type-estimate-union(type-infer-rhs(LT, ?rhs1), 
                             type-infer-rhs(LT, ?rhs-more)) }

  { type-infer-rhs(LTS, ?rhs:expression) }              // Induction rule (3).
    => { type-union-with-sources(make(<type-estimate-bottom>), ?rhs, ?=cache) }

  { type-infer-rhs(LTS, ?rhs1:expression, ?rhs-more:*) }// Induction rule (4).
    => { type-estimate-union(type-infer-rhs(LT, ?rhs1), 
                             type-infer-rhs(LTS, ?rhs-more)) }
end;

// define type-inference-rules empty-rule-set end;            // Empty Test case
define macro type-inference-rules-definer
  // Dispatch on form of first rule, generating code; recurse on rest.
  { define type-inference-rules ?rule-group:name end } => { }    // No rules

  { define type-inference-rules ?rule-group:name
      ?lhs:name :: ?dfm-type:name ?op-and-rhs;                   // First rule
      ?more-rules:*                                              // More rules
    end }
  => { define method type-estimate-infer                         // Expand first
         (?lhs :: ?dfm-type, ?=cache :: <type-cache>) => (te :: <type-estimate>)
         dynamic-bind (?=*current-rule* = ?#"rule-group",// Rule currently firing
		       ?=*current-lhs*  = ?lhs,          // Trigger pattern on lhs
		       ?=*current-rhs*  = #())           // Precious bodily fluids
          with-infer-stepping (?dfm-type)
           let answer = type-infer-rhs(?op-and-rhs);             // Compute type
           type-estimate-update-cache(?lhs, ?=cache, answer);    // Update cache
           answer                                                // Return type
          end
         end
       end;
       define type-inference-rules ?rule-group                   // Expand rest
         ?more-rules 
       end }
op-and-rhs:   // ?#"op" blows up for some obscure reason
  { ==  ?rhs } => { ET, ?rhs }  // Basis rule
  { <-* ?rhs } => { LTS, ?rhs } // Induction rule, last rhs is a sequence
  { <-  ?rhs } => { LT, ?rhs  } // Induction rule
rhs: // ,-separated expressions Should be able to do: { ??item:expression, ... }
  { }                        => { }
  { ?item:expression }       => { ?item }
  { ?item:expression, ?rhs } => { ?item, ?rhs }
end;

///
/// Type inference rules.  Twisted way to do abstract semantic interpretation!
///

define type-inference-rules type-infer-punt
  // Rules which punt on roots of DFM heterarchy.  Anything else will error.
  // <nop>, <if>, some <end>s, <bind>, return no values in 
  // any reasonable sense, so they have no type.
  ignore :: <dfm-ref> == make(<type-estimate-bottom>) // Bottom is punt type
end;

///
/// Type inference for DFM instructions.  There should be a rule here to
/// trigger on every type of <computation>, or else use the punt rule above.
///

define type-inference-rules type-infer-references
  // Rules about reference-like instructions.
  var-ref :: <variable-reference> <- referenced-binding(var-ref); // Defer to var
  int-ref :: <interactor-binding-reference> <- referenced-binding(int-ref); // Defer to var
  clo-ref :: <make-closure>       == if (computation-signature-value(clo-ref))
                                       // *** ? use signature?
                                       lift-model-named(#"<method>")
                                     else
                                       let m = computation-closure-method(clo-ref);
                                       *current-rhs* := add!(*current-rhs*, m);
                                       type-estimate-in-cache(m, cache); 
                                     end;
end;

define type-inference-rules type-infer-assigns
  // Rules about assignment-like instructions.
  assign :: <assignment>          <- computation-value(assign); // Defer to RHS 
  update :: <conditional-update!> == lift-model-named(#"<boolean>");
  txfer  :: <temporary-transfer-computation> 
    <- computation-value(txfer);  // Defer to value
  // <definition> and <set!> are both <assignment>s.
  // <multiple-value-spill> & <multiple-value-unspill> are txfers.
end;

define type-inference-rules type-infer-merges
  // Rules about <merge> & <if> instructions.
  // <if> is pure control, so it gets bottom via the punt rule.
  // "It's the <merge>, stupid." (With apologies to James Carville.)
  // Unary merges all do some sort of inference, so they have heir own rules.
  merge :: <binary-merge> <- merge-left-value(merge), merge-right-value(merge)
end;

define type-inference-rules type-infer-calls
  // Rules about the various kinds of call instructions.
  // Type of call is return-type of callee, _if_ it returns.  Can't infer arg
  // types before call, since it might signal a run-time <type-error>.  _Can_
  // infer post-call arg types, though, when we do flow-dependent types.  
  // The real version of this will use function templates.
  // *** <primitive-indirect-call>
  call :: <c-variable-pointer-call> == lift-model-named(#"<raw-pointer>");
  call :: <call>                    == type-estimate-call-stupidly(call, cache); // ***
  call :: <stack-vector>            == lift-model-named(#"<simple-object-vector>");
  call :: <loop-call>               == make(<type-estimate-bottom>);
  call :: <any-slot-value> 
    == as(<type-estimate>, ^slot-type(computation-slot-descriptor(call)));
  call :: <any-repeated-slot-value> 
    == begin
	 let instance-te = type-estimate(computation-instance(call));
	 if (instance?(instance-te, <type-estimate-limited-collection>))
	   type-estimate-of(instance-te)
	 else 
	   as(<type-estimate>, 
	      repeated-representation
		(^slot-type(computation-slot-descriptor(call))))
	 end if
       end 
end;

/* [gts, 2/98, wait until harp backend ready]
define type-inference-rules type-infer-c-ffi
  call :: <begin-with-stack-structure> == lift-model-named(#"<raw-pointer>");
end;
*/

define type-inference-rules type-infer-blocks
  // Various concrete subclasses of <block>.
  // * A <bind-exit>'s <temporary> these days contains only the result of a call
  //   to the escape continuation; the body result is explicitly merged after 
  //   the <end-exit-block>, so it'll get typed on demand.  Used to type the 
  //   body here, but there were problems with type-estimate-body using the
  //   <end-exit-block> as a guard -- couldn't take previous-computation of it.
  // * An <unwind-protect>'s <temporary> is there, but ununsed.  Use a 
  //   side-effect to fill the cache with the body & cleanups.
  b-x :: <bind-exit>      <-* exits(entry-state(b-x)); // *** Escaped?
  u-p :: <unwind-protect> ==  type-estimate-body(body(u-p), cache); // ***
end;

define type-inference-rules type-infer-ends
  // Various kinds of terminating computations, and related things.
  // NB: the method on <end-block> overrides the one on <end> since the <e*b>s
  //     are built on (<end-block>, <end>) in that order.
  ndr :: <end>       <- computation-value(ndr);       // <return>, <exit>
  ebl :: <end-block> == make(<type-estimate-bottom>); // <eeb>, <epb>, <ecb>
  ebl :: <end-loop>  == make(<type-estimate-bottom>); 
  ext :: <exit>      ==
    begin // *** Dependency!
      let value-type = type-estimate-in-cache(computation-value(ext), cache);
      if (instance?(value-type, <type-estimate-limited-collection>))
        make(<type-estimate-values>, rest: type-estimate-of(value-type))
      else
        make(<type-estimate-values>) // values(#rest <object>).
      end
    end;
end;

///
/// Guiding principles re <bottom> and multiple values: 
///
/// * values(#rest <bottom>), or "bottoms all the way down," is the maximally
///   undefined return value.  No matter which value you try to look at, you get
///   <bottom>.  Even the ones you don't look at are <bottom>.
///
/// * values(...anything..., <bottom>, ...anything...) normalizes to 
///   values(#rest <bottom>).
///
/// * Extracting a value from values(#rest <bottom>) always gives <bottom>, not 
///   <bottom> union singleton(#f), since if you never return, you never default 
///   the return value.
///
/// * Adjusting a values(#rest <bottom>) stays at values(#rest <bottom>), so 
///   no matter what value you later try to extract, you get <bottom>.
///

define function type-estimate-values-element-subtype?
    (values-te :: <type-estimate>, index :: <integer>, te :: <type-estimate>)
 => (subtype? :: <boolean>)
  // Is the indexth value of values-te a subtype of te?  Slightly complex
  // because values-te could be a union of values type-estimates.
  local method single-value-subtype? (values-te :: <type-estimate-values>)
         => (indexth-te-subtype? :: <boolean>)
          let fixed-te* = type-estimate-fixed-values(values-te);
          let rest-te   = type-estimate-rest-values(values-te);
          let single-te =
            if (index < size(fixed-te*))
              // The value extraction is guaranteed to succeed with type:
              fixed-te*[index]
            elseif (~rest-te)
              // No rest type, so must default to #f.
              make(<type-estimate-limited-instance>, singleton: &false)
            elseif (instance?(rest-te, <type-estimate-bottom>))
              // values(#rest <bottom>), can't default #f because never return
              make(<type-estimate-bottom>)
            else 
              // TODO: Where does value padding happen? If in this 
              // instruction, the inferred type should be the rest type
              // (if present) union singleton(#f). That's what I've done
              // here to be conservative.
              // The value extraction may succeed, or be defaulted, 
              // resulting in type:
              type-estimate-union(rest-te, 
                                  make(<type-estimate-limited-instance>, 
                                       singleton: &false))
            end;
          type-estimate-subtype?(single-te, te)
        end;
  select (values-te by instance?)
    <type-estimate-bottom> => #t;  // *** Not strictly <boolean>.
    <type-estimate-values> => single-value-subtype?(values-te);
    <type-estimate-union>  => every?(single-value-subtype?,
                                     type-estimate-unionees(values-te));
  end
end;

define function type-estimate-values-rest-subtype?
    (values-te :: <type-estimate>, index :: <integer>, te :: <type-estimate>)
 => (subtype? :: <boolean>)
  // Is every value of values-te after index a subtype of te?  Slightly complex
  // because values-te could be a union of values type-estimates.
  local method single-rest-subtype? (values-te :: <type-estimate-values>)
          let fixed-te* = type-estimate-fixed-values(values-te);
          let rest-te = type-estimate-rest-values(values-te);
          (~rest-te | type-estimate-subtype?(rest-te, te)) &
            // every type between index and size must be a subtype
            block (exit)
              for (i from index below size(fixed-te*))
                unless (type-estimate-subtype?(fixed-te*[i], te))
                  exit(#f)
                end
              end;
              #t
            end
        end;
  select (values-te by instance?)
    <type-estimate-bottom> => #t;
    <type-estimate-values> => single-rest-subtype?(values-te);
    <type-estimate-union>  => every?(single-rest-subtype?,
                                     type-estimate-unionees(values-te));
  end
end;


/*  Save this for use in <extract-rest-value> type inference below.
define method type-estimate-rest-value (te :: <type-estimate-values>,
                                        index :: <integer>)
 => (rest-value-te :: false-or(<type-estimate>))
  let fixed-te* = type-estimate-fixed-values(te);
  let rest-te = type-estimate-rest-values(te);
  if (index < size(fixed-te*))
    // The value extraction is guaranteed to succeed with type:
    // TODO? This does too much consing.  I assume it almost never
    //       happens, so it shouldn't matter.
    let fixed-type-estimates :: <type-variables>
      = copy-sequence(fixed-te*, start: index);
    make(<type-estimate-union>,
         unionees: if (rest-te)
                     add!(rest-te, fixed-type-estimates)
                   else
                     fixed-type-estimates
                   end)
  else
    rest-te
  end
end;

define method type-estimate-rest-value (te :: <type-estimate-union>,
                                        index :: <integer>)
 => (rest-value-te :: false-or(<type-estimate>))
  // this assumes that all unionees are <type-estimate-values>
  // probably it's an error if that's not true
  // this conses too much, but #rest values aren't used much
  let unionees :: <unionee-sequence>
    = map(rcurry(type-estimate-rest-value, index), 
	  type-estimate-unionees(te));
  if (every?(\~, unionees))
    #f
  else
    make(<type-estimate-union>,
         unionees: remove(unionees, #f))
  end
end;
*/

define method type-estimate-of (te :: <type-estimate-class>)
  let class = type-estimate-class(te);
  if (^subtype?(class, dylan-value(#"<collection>")))
    make(<type-estimate-class>, class: dylan-value(#"<object>"))
  else 
    error("Trying to take type-estimate-of on a non-collection %=", te);
  end if
end method;

// *** Dependencies!
define type-inference-rules type-infer-multiple-values
  val :: <values> == make(<type-estimate-values>,
                          fixed: map(rcurry(type-estimate-in-cache, cache),
                                     fixed-values(val)),
                          rest: when (rest-value(val))
                                  type-estimate-of
				    (type-estimate-in-cache(rest-value(val), cache))
                                end);
  xsv :: <extract-single-value> ==
    begin
      let values-te = type-estimate-in-cache(computation-value(xsv), cache);
      let index = index(xsv);
      local method estimate-single-value (te :: <type-estimate-values>)
             => (indexth-te :: <type-estimate>)
              // *** Share with type-estimate-values-element-subtype?, above.
              let fixed-te* = type-estimate-fixed-values(te);
              let rest-te   = type-estimate-rest-values(te);
              if (index < size(fixed-te*))
                // The value extraction is guaranteed to succeed with type:
                fixed-te*[index];
              elseif (~rest-te)
                // No #rest value, so default to #f
                make(<type-estimate-limited-instance>, singleton: &false)
              elseif (instance?(rest-te, <type-estimate-bottom>))
                // Something like values(#rest <bottom>), so result is bottom
                // because it can't be defaulted to #f.
                make(<type-estimate-bottom>)
              else
                // TODO: Where does value padding happen? If in this
                // instruction, the inferred type should be the rest type
                // (if present) union singleton(#f). That's what I've done
                // here to be conservative.
                // The value extraction may succeed, or be defaulted, 
                // resulting in type:
                type-estimate-union(rest-te, 
                                    make(<type-estimate-limited-instance>, 
                                         singleton: &false))
              end
            end;
        select (values-te by instance?)
        <type-estimate-bottom> => values-te;
        <type-estimate-values> => estimate-single-value(values-te);
        <type-estimate-union>  => make(<type-estimate-union>,
                                       unionees: map(estimate-single-value,
                                                     type-estimate-unionees(
                                                       values-te)));
      end
    end;
  xrv :: <extract-rest-value> 
    == // We're accessing a rest vector, but there's no point in
       // doing anything flash until limited collections are really
       // flying in the typist, hence the following. 
       // TODO: Make this a limited vector of type when possible.
       lift-model-named(#"<simple-object-vector>");
  // <multiple-value-spill> & <multiple-value-unspill> are txfers.
end;

define type-inference-rules type-infer-adjust-multiple-values
  adj :: <adjust-multiple-values> ==
    begin
      let values-te = type-estimate-in-cache(computation-value(adj), cache);
      let  n        = number-of-required-values(adj);
      local method adjust-mv (te :: <type-estimate-values>)
              // Ensure there are EXACTLY n values.
              let fixed-te*         = type-estimate-fixed-values(te);
              let rest-te           = type-estimate-rest-values(te);
              let values-fixed-size = size(fixed-te*);
              if (values-fixed-size = n)
                // Fixed values supply exactly as many as we want
                if (rest-te)
                  // Lose the rest value
                  make(<type-estimate-values>, fixed: fixed-te*, rest: #f)
                else
                  // No rest value to lose, so no change.
                  te
                end
              elseif (n < values-fixed-size)
                // Fixed values supply more than needed; lose rest & some fixed.
                make(<type-estimate-values>, 
                     fixed: copy-sequence(fixed-te*, end: n),
                     rest: #f)
              else
                // n > values-fixed-size, so fixed values insufficient for our
                // needs.  Fill with rest value or singleton(#f).
                let fill-te = 
                  if (~rest-te)
                    // No rest arg, so fill is singleton(#f).
                    make(<type-estimate-limited-instance>, singleton: &false)
                  elseif (instance?(rest-te, <type-estimate-bottom>))
                    // values(#rest <bottom>), so stay bottom!
                    make(<type-estimate-bottom>)
                  else
                    // Use rest value union singleton(#f).
                    type-estimate-union(rest-te,
                                        make(<type-estimate-limited-instance>,
                                             singleton: &false))
                  end;
                make(<type-estimate-values>,
                     fixed: concatenate(fixed-te*,
                                        make(<list>,
                                             size: n - values-fixed-size,
                                             fill: fill-te)),
                     rest: #f)
              end
            end;
      select (values-te by instance?)
        <type-estimate-bottom> => values-te;
        <type-estimate-values> => adjust-mv(values-te);
        <type-estimate-union>  => make(<type-estimate-union>,
                                       unionees: map(adjust-mv,
                                                     type-estimate-unionees(
                                                       values-te)));
      end
    end;
  adj :: <adjust-multiple-values-rest> ==
    begin
      let values-te = type-estimate-in-cache(computation-value(adj), cache);
      let n         = number-of-required-values(adj);
      local method adjust-mv (te :: <type-estimate-values>)
              // Ensure there are at LEAST n fixed values.
              let fixed-te*         = type-estimate-fixed-values(te);
              let rest-te           = type-estimate-rest-values(te);
              let values-fixed-size = size(fixed-te*);
              if (values-fixed-size >= n)
                // Fixed values will suffice for our needs.
                te
              else
                // Need to pad out fixed values with rest or #f.
                let fill-te =
                  if (~rest-te)
                    // No rest type, so pad with #f
                    make(<type-estimate-limited-instance>, singleton: &false)
                  elseif (instance?(rest-te, <type-estimate-bottom>))
                    // Sucked into the values(#rest <bottom>) black hole.
                    make(<type-estimate-bottom>)
                  else
                    // Otherwise use rest type union singleton(#f).
                    type-estimate-union(rest-te, 
                                        make(<type-estimate-limited-instance>,
                                             singleton: &false))
                  end;
                make(<type-estimate-values>, 
                     fixed: concatenate(fixed-te*,
                                        make(<list>,
                                             size: n - values-fixed-size,
                                             fill: fill-te)),
                     rest: rest-te)
              end
            end;
      select (values-te by instance?)
        <type-estimate-bottom> => values-te;
        <type-estimate-values> => adjust-mv(values-te);
        <type-estimate-union> => make(<type-estimate-union>,
                                      unionees: map(adjust-mv,
                                                    type-estimate-unionees(
                                                      values-te)));
      end
    end;
end;

define generic constant-value? (ref)
  => (constant? :: <boolean>, value :: <object>);

define method constant-value? 
  (ref :: <object-reference>)
   => (constant-value? :: <boolean>, constant-value)
  // Extract the constant from an <object-reference>.
  values(#t, reference-value(ref))
end method;

define method constant-value? 
  (ref :: <defined-constant-reference>)
   => (constant-value? :: <boolean>, constant-value)
  // Extract the constant from an <defined-constant-reference>.
  // TODO: DOESN'T HANDLE FALSE
  let value = computation-value(ref);
  if (value)
    let (inlineable?, inline-value) = inlineable?(value);
    if (inlineable?)
      values(#t, inline-value)
    else
      values(#f, #f)
    end if
  else
    values(#f, #f)
  end if;
end method;

define method constant-value?
    (ref :: <temporary>) => (constant-value? :: <boolean>, constant-value)
  // If this temporary is estimated as a singleton, extract the constant.
  let type = type-estimate(ref);
  if (instance?(type, <type-estimate-limited-instance>))
    values(#t, type-estimate-singleton(type))
  else
    values(#f, #f)
  end
end method;

define method constant-value?
  (ref :: <value-reference>) => (constant-value? :: <boolean>, constant-value)
  // Other kinds of <value-reference>s are not constants.
  values(#f, #f)
end method;

define method constant-value-in-cache?
    (ref :: <value-reference>, cache)
  => (constant? :: <boolean>, value :: <object>);
  constant-value?(ref)
end method;

define method constant-value-in-cache?
    (ref :: <temporary>, cache)
 => (constant? :: <boolean>, value :: <object>);
  let type = type-estimate-in-cache(ref, cache);
  if (instance?(type, <type-estimate-limited-instance>))
    values(#t, type-estimate-singleton(type))
  else
    values(#f, #f)
  end
end method;


define function poor-mans-check-type-intersection
    (value-type :: <type-estimate>, temp :: false-or(<value-reference>), 
       cache :: <type-cache>)
 => (intersection :: <type-estimate>)
  // TODO: take intersection of value type and checked type
  //       for now, do a poor-man's intersection
  if (temp)
    let (the-type-constant?, the-type) = constant-value-in-cache?(temp, cache);
    if (the-type-constant? & instance?(the-type, <&type>))
      let checked-type = as(<type-estimate>, the-type);
      if (type-estimate-subtype?(value-type, checked-type))
        // Value-type is more specific
        value-type
      else
        // Checked-type is more specific
        checked-type
      end
    else
      // don't know the type being checked at compile time
      value-type
    end
  else
    // optimizer has determined that type check is superfluous
    value-type
  end
end;

define type-inference-rules type-infer-checks
  // <check-type>s are a type check + temporary transfer
  ct :: <check-type> ==
    poor-mans-check-type-intersection(type-estimate-in-cache(computation-value(ct), cache),
                                      type(ct),
                                      cache);
  // <constrain-type>s are a constraint + temporary transfer
  // See description in optimization/assignment for motivation.
  ct :: <constrain-type> == 
    begin
      let values-te = type-estimate-in-cache(computation-value(ct), cache);
      let pruned-values-te = 
        if (ct.type)
          poor-mans-check-type-intersection(values-te, ct.type, cache)
	else
          let false 
            = make(<type-estimate-limited-instance>, singleton: &false);
          type-difference(values-te, false) | false;
	end;
      /*
      unless (type-estimate-subtype?(values-te, pruned-values-te))
        format-out(">>> Pruning type for %=:\n  Before %=\n  After  %=\n", 
                   computation-value(ct), values-te, pruned-values-te);
      end;
      */
      pruned-values-te
    end;
  ct :: <multiple-value-check-type> ==
    begin
      let values-te          = type-estimate-in-cache(computation-value(ct), cache);
      let checked-types      = types(ct);
      let checked-fixed-size = size(checked-types);
      local method mv-intersection (te :: <type-estimate-values>)
              // Intersect te elements with checked-types, in order.  Make sure
              // we have exactly that many values, trimming #rest type.
              let fixed-te*         = type-estimate-fixed-values(te);
              let values-fixed-size = size(fixed-te*);
              let result-fixed      = make(<vector>, size: checked-fixed-size);
              // Fill result-fixed w/intersection of inferred & checked types.
              map-into(result-fixed,
                       rcurry(poor-mans-check-type-intersection, cache),
                       fixed-te*, checked-types);
              when (values-fixed-size < checked-fixed-size)
                // Insufficient fixed values, so pad with rest value.
                let rest-te          = type-estimate-rest-values(te);
                let rest-values-type =
                  if (~rest-te)
                    // No rest type, so use default #f.
                    make(<type-estimate-limited-instance>, singleton: &false)
                  elseif (instance?(rest-te, <type-estimate-bottom>))
                    // <bottom> contagion
                    make(<type-estimate-bottom>)
                  else
                    // Union rest-te with singleton(#f).
                    type-estimate-union(rest-te, 
                                        make(<type-estimate-limited-instance>, 
                                             singleton: &false))
                  end;
                for (i from values-fixed-size below checked-fixed-size)
                  // Pad out checked types with intersection of checked
                  // type and the fill type.
                  result-fixed[i] :=
                    poor-mans-check-type-intersection(rest-values-type,
                                                      checked-types[i],
                                                      cache)
                end
              end;
              // Result-fixed now contains the types we want.
              make(<type-estimate-values>, 
                   fixed: as(<list>, result-fixed), // Someday use <sequence>...
                   rest: #f)
            end;
      select (values-te by instance?)
        <type-estimate-bottom> => values-te;
        <type-estimate-values> => mv-intersection(values-te);
        <type-estimate-union>  => make(<type-estimate-union>,
                                       unionees: map(mv-intersection,
                                                     type-estimate-unionees(
                                                       values-te)));
      end
    end;
  ct :: <multiple-value-check-type-rest> ==
    begin
      let values-te          = type-estimate-in-cache(computation-value(ct), cache);
      let checked-types      = types(ct);
      let checked-fixed-size = size(checked-types);
      local method mv-intersection (te :: <type-estimate-values>)
              // Intersect te elements with checked-types, in order.  Make sure
              // we have AT LEAST that many values, preserving #rest type.
              let fixed-te*         = type-estimate-fixed-values(te);
              let values-fixed-size = size(fixed-te*);
              let result-fixed      = make(<vector>, 
                                           size: max(values-fixed-size,
                                                     checked-fixed-size));
              // Fill result-fixed w/ intersection of inferred & checked types.
              map-into(result-fixed,
                       rcurry(poor-mans-check-type-intersection, cache),
                       fixed-te*, checked-types);
              let rest-te = type-estimate-rest-values(te);
              when (values-fixed-size < checked-fixed-size)
                // Insufficient fixed values, so pad with rest value.
                let rest-values-type =
                  if (~rest-te)
                    // No rest type, so default to singleton(#f).
                    make(<type-estimate-limited-instance>, singleton: &false)
                  elseif (instance?(rest-te, <type-estimate-bottom>))
                    // <bottom> contagion
                    make(<type-estimate-bottom>)
                  else
                    // Use rest type union singleton(#f)
                    type-estimate-union(rest-te, 
                                        make(<type-estimate-limited-instance>, 
                                             singleton: &false))
                  end;
                for (i from values-fixed-size below checked-fixed-size)
                  // Pad out checked types with intersection of checked
                  // type and the fill type.
                  result-fixed[i] :=
                    poor-mans-check-type-intersection(rest-values-type,
                                                      checked-types[i],
                                                      cache)
                end
              end;
              // Now make sure rest of fixed types, if any, are compatible 
              // with checked-rest-type.
              let checked-rest-type = rest-type(ct);
              for (i from checked-fixed-size below values-fixed-size)
                result-fixed[i] :=
                  poor-mans-check-type-intersection(fixed-te*[i],
                                                    checked-rest-type,
                                                    cache)
              end;
              // Result-fixed now contains the fixed types.  Also need to 
              // check if the rest type, if any, is compatible.
              make(<type-estimate-values>,
                   fixed: as(<list>, result-fixed),
                   rest: rest-te &
                         poor-mans-check-type-intersection(rest-te,
                                                           checked-rest-type,
                                                           cache))
            end;
      select (values-te by instance?)
        <type-estimate-bottom> => values-te;
        <type-estimate-values> => mv-intersection(values-te);
        <type-estimate-union>  => make(<type-estimate-union>,
                                       unionees: map(mv-intersection,
                                                     type-estimate-unionees(
                                                       values-te)));
      end
    end;
end;

// Assume except is just singleton(#f) for now!

define method type-difference
  (type :: <type-estimate>, except :: <type-estimate-limited-instance>)
  type
end;

define method type-difference
  (type :: <type-estimate-class>, except :: <type-estimate-limited-instance>)
  if (type.type-estimate-class == dylan-value(#"<boolean>"))
    make(<type-estimate-limited-instance>, singleton: &true);
  else
    type
  end
end;

define method type-difference
  (type :: <type-estimate-union>, except :: <type-estimate-limited-instance>)
  collecting (unionees)
    for (te :: <type-estimate> in type-estimate-unionees(type))
      let pruned-te = type-difference(te, except);
      if (pruned-te) collect-into(unionees, pruned-te) end;
    end;
    let unionees :: <simple-object-vector> 
      = as(<simple-object-vector>, collected(unionees));
    select (unionees.size)
      0 => #f;
      1 => unionees[0];
      otherwise => make(<type-estimate-union>, unionees: unionees)
    end;
  end collecting;
end;

define method type-difference
  (type :: <type-estimate-limited-instance>, 
     except :: <type-estimate-limited-instance>)
  if (type-estimate-singleton(type) == type-estimate-singleton(except))
    #f
  else
    type
  end;
end method;

define type-inference-rules type-infer-cells
  // The Marquess of Queensbury Rules.
  //
  // By popular demand, boxes have the same type as their contents.
  // This is a storage issue instead of a type issue, and the compiler is the
  // one generating all the boxes, so elaborating a boxed type would just check
  // for compiler code-generation bugs anyway.
  //
  // Readers of the box get a type from original value + all assigns. (reflow)
  // Writers of the box get a type from what they write there.
  mb :: <make-cell>       
    <-  computation-value(mb);
  gv :: <get-cell-value>  
    <-* generator(computation-cell(gv)), assignments(temporary(generator(computation-cell(gv))));
  sv :: <set-cell-value!> 
    <-  computation-value(sv);
end;

define type-inference-rules type-infer-guarantee-type
  gt :: <guarantee-type> ==
    begin
      let static-type = static-guaranteed-type(gt);
      if (static-type)
          as(<type-estimate>, static-type)
      else
        poor-mans-check-type-intersection(type-estimate-in-cache(computation-value(gt),
								 cache),
                                          guaranteed-type(gt),
                                          cache)
      end
    end;
end;

///
/// Type inference on variables (subclasses of <binding> and <temporary>).
///

define type-inference-rules type-infer-variables
  // Rules about variable-like things: 
  // <temporary>, <multiple-value-temporary>, <entry-state>
  // <binding>, <module-binding>, <lexical-required-variable>, 
  // <lexical-keyword-variable>, <lexical-rest-variable>, 
  // <lexical-specialized-variable>.
  // Note that any <temporary> with a generator is handled by type-infer, now.
  mb   :: <module-binding>
    == case
         constant?(mb) // module constant
           => let (val, computed?)
                   = binding-constant-model-object(mb, error-if-circular?: #f);
              case 
                // Initializer has been computed, so use its result type.
                computed? & inlineable?(val)
                  => *current-rhs* := add!(*current-rhs*, val);
		     type-estimate-in-cache(val, cache);
		computed? & ^instance?(val, dylan-value(#"<object>"))
		  // Not inlineable, but might be able to extract a type from val.
                  // Also not a raw type, which would cause problems.
		  => // This does what type-estimate would have done with val,
		     // but using ^object-class rather than a singleton type.
		     *current-rhs* := add!(*current-rhs*, val);
		     cache[val]    := make(<type-variable>);
		     let answer = as(<type-estimate>, ^object-class(val));
		     type-estimate-update-cache(val, cache, answer);
		     answer;
                // Initializer not computed, so have to punt to declared type.
                otherwise 
                  => type-infer-using-declared-type(cache, mb);
              end;
         // Otherwise a module variable: type from decls, initializer, assigns.
         // NB: Raw variables REQUIRE decls, since they're not <object>s!
         // Exported: must believe decl (can't find all assigns).
         // TODO: SMARTNESS: This queries exported/created from the module, 
         // not the library. The test should be a test for escaping the
         // library, not the module.
         // TODO: Module variables can also escape a library by being named
         // in a macro.
         exported?(mb)
           => type-infer-using-declared-type(cache, mb);
         otherwise 
           => type-infer-using-declared-type(cache, mb);
           /*
           // Unexported module variable: take union of all assigns (including
           // initializer), which is the "real" type.  NB: type-safety with
           // the declared type is enforced with assignment type checks, so 
           // there's no need to try to intersect them here.
           // Further weirdness: optimizers might have removed an assignment,
           // leaving behind a model object.  So initial type should be type of
           // model object or bottom if none.
           let model = binding-model-object(mb, default: not-found(),
                                            error-if-circular?: #f);
           type-union-with-sources(case
                                     found?(model) =>
                                       *current-rhs* := add!(*current-rhs*, model);
                                       type-estimate-in-cache(model, cache);
                                     otherwise => make(<type-estimate-bottom>);
                                   end,
                                   map(computation-value, assignments(mb)),
                                   cache);
         */
       end;
  // A fallback for lexical variables which are a subclasses of <temporary>, but
  // don't always have their generator set, until all particular cases are
  // filled in.
  // N.B. - any <temporary> with a generator is filtered out before here
  lv :: <lexical-variable> == lift-model-named(#"<object>");
  lsv :: <lexical-specialized-variable> // includes required & local vars
    == type-union-with-sources
         (begin
            let spec = specializer(lsv); // Start @ decl
            select (spec by instance?)
              <&type>   => as(<type-estimate>, spec);
              otherwise => lift-model-named(#"<object>"); // Dynamic?
            end
          end,
            assignments(lsv),
          cache);
  // lkv    :: <lexical-keyword-variable> == ***;
  lrv :: <lexical-rest-variable> 
    == lift-model-named(#"<simple-object-vector>");
  // We know nothing...
  ib :: <interactor-binding> 
    == lift-model-named(#"<object>");
end;

define function type-infer-using-declared-type
    (cache, mb :: <module-binding>) => (answer :: <type-estimate>)
  let (type-val, computed?)
    = binding-constant-type-model-object
        (mb, error-if-circular?: #f);
  if (computed?)
    as(<type-estimate>, type-val);
  else
    lift-model-named(#"<object>");
  end;
end function;

///
/// Type inference on various kinds of data (subclasses of <&object>).
///

define type-inference-rules type-infer-data
  // Rules about data objects.
  obj :: <&top>      == type-estimate-datum(obj);
  obj :: <heap-deferred-model> == type-estimate-datum(obj);
  // *** All of these are because of mapped types, which are implemented
  //     directly in the compiler (hence not under <&object>), and because
  //     the emulator doesn't do type-unions meaningfully.
  obj :: <number>           == type-estimate-datum(obj);
  obj :: <boolean>          == type-estimate-datum(obj);
  obj :: <character>        == type-estimate-datum(obj);
  obj :: <string>           == type-estimate-datum(obj);
  obj :: <symbol>           == type-estimate-datum(obj);
  obj :: <vector>           == type-estimate-datum(obj);
  obj :: <list>             == type-estimate-datum(obj);
  obj :: <mapped-unbound>   == type-estimate-datum(obj);
end;