o
    qoi                     @   s  d Z ddlZddlZddlZddlZddlZddlZddlZddlZddl	m
Z
mZmZmZmZmZ ddlmZ ddlmZ ddlmZ ddlmZ ddlmZ dd	lmZ dd
lmZ ddlmZ ddlmZ ddlZdZdZdZ dZ!dZ"ej#g g g g g dZ$e%dd ej&' D Z(edede
f dZ)ej*ddG dd dZ+G dd de,Z-G dd dej.Z/d d! Z0d"ej1d#ej2d$ej1fd%d&Z3d'ej4d(e5fd)d*Z6d'ej4d#ej2fd+d,Z7d-d. Z8d/d0 Z9d1d2 Z:d3e)d$e)fd4d5Z;ej*ddG d6d7 d7Z<	dUd8d8dde< dd9d:e=d;ee= d<eej> d=e<d>e=d$e
fd?d@Z?	dUddAd;ee= d$e
fdBdCZ@dDe
d$e=fdEdFZAdGejBfdHdIZCd#e
d$e5fdJdKZDd#e
d$e=fdLdMZEG dNdO dOejFZGd#ede
f d$e5fdPdQZHdRede
f d$ede
f fdSdTZIdS )Va  Provides utilities for transforming builder functions into `fdl.Config`s.

This module defines the `auto_config` function (and associated helpers), which
can be used to convert an existing function that creates an object graph into a
function that creates a graph of `Config` and `Partial` objects. When the
resulting graph of `Config` and `Partial` objects is built via `fdl.build()`, it
will yield same object graph as the original function.
    N)AnyCallableOptionalTypeTypeVarcast)arg_factory)building)casting)config)copying)mutate_buildable)partial)auto_config_policy)daglish_legacy__auto_config_call_handler__!__auto_config_attr_load_handler__!__auto_config_attr_save_handler___attr_save_temp__auto_config_closure_wrapper__)posonlyargsargs
kwonlyargskw_defaultsdefaultsc                 C   s$   g | ]}t |st |r|qS  )inspect	isroutineisclass).0builtinr   r   X/home/ubuntu/.local/lib/python3.10/site-packages/fiddle/_src/experimental/auto_config.py
<listcomp>6   s    r"   _GenericCallable.)boundT)frozenc                       s   e Zd ZU dZedef ed< edejf ed< e	ed< e
dd Zdd	 Zd
efddZd
ejfddZdddZe
dd Z fddZ  ZS )
AutoConfiga<  A function wrapper for auto_config'd functions.

  In order to support auto_config'ing @classmethod's, we need to customize the
  descriptor protocol for the auto_config'd function. This simple wrapper type
  is designed to look like a `functool.wraps` wrapper, but implements custom
  behavior for bound methods.
  .funcbuildable_funcalways_inlinec                 C   s   dS NTr   selfr   r   r!   nowrapN   s   zAutoConfig.nowrapc              	   C   s@   dD ]}zt | j|}W n	 ty   Y qw t| || qd S )N)
__module____name____qualname____doc____annotations__)getattrr'   AttributeErrorobject__setattr__)r,   namevaluer   r   r!   __post_init__R   s   zAutoConfig.__post_init__returnc                 O      | j |i |S Nr'   r,   r   kwargsr   r   r!   __call__c      zAutoConfig.__call__c                 O   r;   r<   r(   r>   r   r   r!   as_buildablef   rA   zAutoConfig.as_buildableNc                 C   s$   t | j||| j||| jdS )Nr'   r(   r)   )r&   r'   __get__r(   r)   )r,   objobjtyper   r   r!   rE   i   s
   zAutoConfig.__get__c                 C   s   | j S r<   r=   r+   r   r   r!   __wrapped__r   s   zAutoConfig.__wrapped__c                    s   t t d|S )Nr'   )r3   super__getattribute__)r,   r7   	__class__r   r!   __getattr__v   s   zAutoConfig.__getattr__r<   )r/   r.   r0   r1   r   r   r2   r   	Buildableboolpropertyr-   r9   r@   rC   rE   rH   rM   __classcell__r   r   rK   r!   r&   @   s   
 

	
r&   c                   @   s   e Zd ZdS )!UnsupportedLanguageConstructErrorN)r/   r.   r0   r   r   r   r!   rR   |   s    rR   c                   @   s  e Zd ZdZ	d;dededefddZdejfd	d
Z	d;dejde
fddZdd ZdejfddZdejfddZdejfddZdejfddZdejfddZdejfddZdejfddZdejfddZdejfd d!Zdejfd"d#Z dej!fd$d%Z"dej#fd&d'Z$dej%fd(d)Z&dej%fd*d+Z'dej(fd,d-Z)dej*fd.d/Z+dej,fd0d1Z-dejfd2d3Z.dej/fd4d5Z0dej1fd6d7Z2dej3fd8d9Z4d:S )<_AutoConfigNodeTransformerzEA NodeTransformer that adds the auto-config call handler into an AST.Fsourcefilenameline_numberc                 C   s,   |  | _|| _|| _|| _d| _d| _dS )a>  Initializes the auto config node transformer instance.

    Args:
      source: The source code of the node that will be transformed by this
        instance. This is used for better error reporting.
      filename: The filename `source` is from.
      line_number: The line number of `source` within `filename`.
      allow_control_flow: Whether to permit control flow constructs (loops,
        conditionals, comprehensions, etc). By default, this is `False`, and
        control flow constructs will cause an
        `UnsupportedLanguageConstructError` to be raised.
    r   N)
splitlines_lines	_filename_line_number_allow_control_flow_function_def_depth_temp_var_count)r,   rT   rU   rV   allow_control_flowr   r   r!   __init__   s   

z#_AutoConfigNodeTransformer.__init__nodec                 C   s0   | j |j d }| j|jd  }| j||j|fS N   )rZ   linenorX   rY   
col_offset)r,   r`   rV   liner   r   r!   _location_for   s   z(_AutoConfigNodeTransformer._location_foractivatablec                 C   s6   | j r
|r
| |S dt|j d}t|| |)NzControl flow (z ) is unsupported by auto_config.)r[   generic_visittyper/   rR   rf   )r,   r`   rg   msgr   r   r!   _handle_control_flow   s   

z/_AutoConfigNodeTransformer._handle_control_flowc              	   C   s:   z|  j d7  _ | |W |  j d8  _ S |  j d8  _ w ra   )r\   rh   r,   r`   r   r   r!   _generic_visit_inside_function   s   
 z9_AutoConfigNodeTransformer._generic_visit_inside_functionc                 C   s   g }|j D ]"}t|tjr|j}t|tjr|j}t|tjr"|j}|	| qz|
d}W n
 ty9   Y dS w |d| D ]}|dv r]td| d|j d| j d| j d| d	q@dS )
a  Validates that decorators are applied in the right order.

    This is done on a best effort basis to catch cases where @classmethod or
    @staticmethod are applied on top of @auto_config.

    Args:
      node: The `ast.FunctionDef` node to validate decorators for.
    auto_configN)classmethodstaticmethod@z' placed above @auto_config on function z at :z;. Reorder decorators so that @auto_config is placed above @.)decorator_list
isinstanceastCallr'   	AttributeattrNameidappendindex
ValueErrorAssertionErrorr7   rY   rZ   )r,   r`   rt   	decoratorauto_config_indexr   r   r!   _validate_decorator_ordering   s6   	
z7_AutoConfigNodeTransformer._validate_decorator_orderingc                    sF   t jt jtt  d|jg fdd|jD  fdd|jD dS )Nr{   ctxc                 3   s    | ]}  |V  qd S r<   visitr   argr+   r   r!   	<genexpr>   s    z8_AutoConfigNodeTransformer.visit_Call.<locals>.<genexpr>c                    s   g | ]}  |qS r   r   )r   keywordr+   r   r!   r"          z9_AutoConfigNodeTransformer.visit_Call.<locals>.<listcomp>r'   r   keywords)rv   rw   rz   _CALL_HANDLER_IDLoadr'   r   r   rl   r   r+   r!   
visit_Call   s
   z%_AutoConfigNodeTransformer.visit_Callc                 C   sL   t |jtjr!tjtjtt d| |jtj	|j
dgg dS | |S Nr   )sr   )ru   r   rv   r   rw   rz   _ATTR_LOAD_HANDLER_IDr   r8   Strry   rh   rl   r   r   r!   visit_Attribute   s   
z*_AutoConfigNodeTransformer.visit_Attributec           
         s   fdd}  |j|_t|jdkr,t|jd tjr,||jd j|jd j|jS  fdd}g }|jD ]}t|tjsEt|tj	ryg }|j
D ]*}t|tjro| }|| ||j|j|}	||	   jd7  _qJ|| qJ||_
q7t|tjr| }||j|j|}	||	 ||j|j|<   jd7  _q7t|tjr  |j|_  |j|_q7t|tjrq7t|tjrq7td| d|d| |S )	z"Handler assignment transformation.c                    sH   t | tjr | } ttjtjtt d| tj	|d|gg dS r   )
ru   rv   rx   r   Exprrw   rz   _ATTR_SAVE_HANDLER_IDr   r   )rF   ry   r8   r+   r   r!   make_expr_call   s   
z?_AutoConfigNodeTransformer.visit_Assign.<locals>.make_expr_callrb   r   c                     s"   t jt d j t  d} | S )N_r   )rv   rz   _ATTR_SAVE_TEMP_VAR_IDr]   Store)temp_varr+   r   r!   make_temp_var  s
   z>_AutoConfigNodeTransformer.visit_Assign.<locals>.make_temp_varz$Cannot handle Assign statement with z as target.)r   r8   lentargetsru   rv   rx   ry   TupleListeltsr|   r]   r}   	SubscriptsliceStarredrz   NotImplementedErrorinsert)
r,   r`   r   r   transformed_nodestargetnew_eltseltr   	expr_noder   r+   r!   visit_Assign   sL    





z'_AutoConfigNodeTransformer.visit_Assignc                 C      | j |ddS NT)rg   rk   rl   r   r   r!   	visit_For.     z$_AutoConfigNodeTransformer.visit_Forc                 C   r   r   r   rl   r   r   r!   visit_While1  r   z&_AutoConfigNodeTransformer.visit_Whilec                 C   r   r   r   rl   r   r   r!   visit_If4  r   z#_AutoConfigNodeTransformer.visit_Ifc                 C   r   r   r   rl   r   r   r!   visit_IfExp7  r   z&_AutoConfigNodeTransformer.visit_IfExpc                 C   r   r   r   rl   r   r   r!   visit_ListComp:  r   z)_AutoConfigNodeTransformer.visit_ListCompc                 C   r   r   r   rl   r   r   r!   visit_SetComp=  r   z(_AutoConfigNodeTransformer.visit_SetCompc                 C   r   r   r   rl   r   r   r!   visit_DictComp@  r   z)_AutoConfigNodeTransformer.visit_DictCompc                 C   r   r   r   rl   r   r   r!   visit_GeneratorExpC  r   z-_AutoConfigNodeTransformer.visit_GeneratorExpc                 C   
   |  |S r<   r   rl   r   r   r!   	visit_TryF     
z$_AutoConfigNodeTransformer.visit_Tryc                 C   r   r   r   rl   r   r   r!   visit_RaiseI  r   z&_AutoConfigNodeTransformer.visit_Raisec                 C   r   r<   r   rl   r   r   r!   
visit_WithL  r   z%_AutoConfigNodeTransformer.visit_Withc                 C   r   r<   r   rl   r   r   r!   visit_YieldO  r   z&_AutoConfigNodeTransformer.visit_Yieldc                 C   r   r<   r   rl   r   r   r!   visit_YieldFromR  r   z*_AutoConfigNodeTransformer.visit_YieldFromc                 C   sH   | j dkrd}t|| || | |j}g |_| |}||_|S )zTransforms a FunctionDef node.r   z=Nested function definitions are not supported by auto_config.)r\   rR   rf   r   rt   rm   )r,   r`   rj   rt   r   r   r!   visit_FunctionDefU  s   


z,_AutoConfigNodeTransformer.visit_FunctionDefc                 C   s(   | j dkrd}t|| || |S )Nr   z4Lambda definitions are not supported by auto_config.)r\   rR   rf   rm   r,   r`   rj   r   r   r!   visit_Lambdad  s   

z'_AutoConfigNodeTransformer.visit_Lambdac                 C      d}t || |)Nz3Class definitions are not supported by auto_config.rR   rf   r   r   r   r!   visit_ClassDefk     z)_AutoConfigNodeTransformer.visit_ClassDefc                 C   r   )Nz<Async function definitions are not supported by auto_config.r   r   r   r   r!   visit_AsyncFunctionDefo  r   z1_AutoConfigNodeTransformer.visit_AsyncFunctionDefN)F)5r/   r.   r0   r1   strintr_   rv   ASTrf   rO   rk   rm   FunctionDefr   rw   r   rx   r   Assignr   Forr   Whiler   Ifr   IfExpr   ListCompr   SetCompr   DictCompr   GeneratorExpr   Tryr   r   Withr   Yieldr   	YieldFromr   r   Lambdar   ClassDefr   AsyncFunctionDefr   r   r   r   r!   rS      sD    
$	JrS   c                    s    d  fdd}t ||   S )z9Returns `True` if `structure` contains a `fdl.Buildable`.Fc                 3   s     t |tjrd d S d V  d S r*   )ru   r   rN   )unused_pathr8   contains_buildabler   r!   traversez  s
   
z%_contains_buildable.<locals>.traverse)r   traverse_with_path)	structurer   r   r   r!   _contains_buildablev  s   r   modulefnr:   c                    sl   dd  t jdd fdd|jjtttf D }t jt jt	t
g || jg dgg d}t |}|S )	a  Wraps `module.body` in a function that defines closure variables for `fn`.

  If `fn` has any free variables (i.e., it's `__code__.co_freevars` is not
  empty), we want to make sure that compiling its AST (assumed to be in the body
  of `module`) will create the same set of free variables in the resulting code
  object. However, by default this won't happen, since we would be compiling
  `fn`'s AST in the absence of its original context (e.g., just compiling a
  nested function, and not the containing one).

  To work around this issue, this function wraps `module.body` in another
  `FunctionDef` that defines dummy variables corresponding to `fn`'s free
  variables. This causes the subsequent compile step to create the right set of
  free variables, and allows us to use `fn.__closure__` when creating a
  new function object via `types.FunctionType`.

  We also add <_CALL_HANDLER_ID> as a final dummy variable, and append its value
  (the call handler) to `fn.__closure__` when creating the new function object.

  Effectively, this wrapping looks like the following Python code:

      def __auto_config_closure_wrapper__():
        closure_var_1 = None
        closure_var_2 = None
        ...
        <_CALL_HANDLER_ID> = None

        def fn(...):  # Or some expression involving a lambda.
          ...  # Contains references to the closure variables.

  Args:
    module: An `ast.Module` object whose body contains the function definition
      for `fn` (e.g., as an `ast.FunctionDef` or `ast.Lambda`).
    fn: The function to create dummy closure variables for (assumed to
      correspond to the body of `module`).

  Returns:
    A new `ast.Module` containing an additional wrapper `ast.FunctionDef` that
    defines dummy closure variables.
  c                 S   s   t j| t  dS )Nr   )rv   rz   r   )r7   r   r   r!   <lambda>  s    z4_wrap_ast_for_fn_with_closure_vars.<locals>.<lambda>N)r8   c                    s    g | ]}t j |gd qS ))r   r8   )rv   r   )r   var_nameast_nameast_noner   r!   r"     s    z6_wrap_ast_for_fn_with_closure_vars.<locals>.<listcomp>)r7   r   bodyrt   )r   type_ignores)rv   Constant__code__co_freevarsr   r   r   Moduler   _CLOSURE_WRAPPER_ID_EMPTY_ARGUMENTSr   fix_missing_locations)r   r   closure_var_definitionswrapper_moduler   r   r!   "_wrap_ast_for_fn_with_closure_vars  s.   +
r   codefn_namec                    s8    fdd| j D } t| dksJ d d| d S )z?Finds the code object within `code` corresponding to `fn_name`.c                    s$   g | ]}t |r|j kr|qS r   )r   iscodeco_name)r   constr   r   r!   r"     s    
z'_find_function_code.<locals>.<listcomp>rb   z Couldn't find function code for rs   r   )	co_constsr   )r   r   r   r   r!   _find_function_code  s
   
r   c                 C   s   t | t} t | |j} | S )ac  Unwraps `code` to find the code object for `fn`.

  This function assumes `code` is the result of compiling an `ast.Module`
  returned by `_wrap_node_for_fn_with_closure_vars`.

  Args:
    code: A code object containing code for `fn`.
    fn: The function to find a code object for within `code`.

  Returns:
    The code object corresponding to `fn`.
  )r   r   r/   )r   r   r   r   r!   _unwrap_code_for_fn  s   
r   c                    s6   t tdr
t S  fdd}t|jd }| S )z#Returns `types.CellType(contents)`.CellTypec                      s    S r<   r   r   contentsr   r!   r     s    z$_make_closure_cell.<locals>.<lambda>r   )hasattrtypesr   ri   __closure__)r  dummy_fn	cell_typer   r  r!   _make_closure_cell  s
   

r  c                 C   s6   t |tjrt| |S t|r| |S td|)a  Converts an argument of an arg_factory partial() to Fiddle buildables.

  In normal Python, one expresses arg factories like,

  my_fn = arg_factory.partial(fn, foo=foo_factory, bar=bar_factory)

  where `foo_factory` produces a `foo` and `bar_factory` produces a `bar`. These
  are called each time `my_fn` is called.

  The Fiddle configuration for `my_fn`, on the other hand, looks like,

  my_fn_config = fdl.Partial(fn, foo=fdl.ArgFactory(foo_factory),
                             bar=fdl.ArgFactory(bar_factory))

  Therefore, we need to wrap `foo_factory` and `bar_factory` in
  `partial.ArgFactory`. Or, if they are already callable sub-configs, then we
  wrap them in ArgFactory.

  If `foo_factory` or `bar_factory` is not a callable or fdl.Partial, then we
  raise an error. It's techincally possible to pass `foo_factory` as a
  fdl.Config object that, when called, returns another fucntion, but this is
  most likely a mistake in configuration, so we don't allow it.

  Args:
    arg_factory_cls: The type to use when creating the ArgFactory (normally this
      will just be `fdl.ArgFactory`, but can potentially be customized).
    arg: Intermediate value passed to `arg_factory.partial`.

  Returns:
    ArgFactory version of a configuration or callable.
  z|Couldn't figure out how to handle arg_factory argument; please bind any constant args with a nested functools.partial. Arg: )ru   r   Partialcast_libr   callabler~   )arg_factory_clsr   r   r   r!   _maybe_as_arg_factory  s    r  c                 O   sF   t |tjr|rtd|tj|fi |S | |g|R i |S )a  Makes a fdl.Partial, but calling appropriate APIs if casting is required.

  Args:
    partial_cls: The type to use when creating the Partial (normally this will
      just be `fdl.Partial`, but can potentially be customized).
    buildable_or_callable: Callable or existing configuration object to update.
    *args: Positional arguments, only supported when `config_or_callable` is a
      Partial already.
    **kwargs: Keyword arguments.

  Returns:
    New callable.
  zFor chained functools.partial calls inside auto_config, e.g. functools.partial(functools.partial(foo, ...), ...), only keyword arguments can be supplied to the outer call. Got: )ru   r   r	  r~   r   	copy_with)partial_clsbuildable_or_callabler   r?   r   r   r!   _make_partial  s   r  	fn_or_clsc                 C   s   t | | ddS )aF  Wrap a callable so that it's exempted from auto_config.

  This can be used either as a decorator to exempt a function, or used inside
  an auto_config function to inline exempt certain calls to a function.
  During auto_config transformation, exempted function calls will be evaluated
  normally rather than turned into a config object. For example::

      @exempt
      def my_square(x): return x * x

      @auto_config
      def build_model():
        return Model(a=np.square(3), b=exempt(np.square)(3), c=my_square(3))

      config = build_model.as_buildable()
      assert config.a == fdl.Config(np.square, 3)
      assert config.b == config.c == 9

  Args:
    fn_or_cls: Any callable.

  Returns:
    A wrapped version of the same callable that will not be transformed to
    config if called inside an auto_config function.
  TrD   r&   )r  r   r   r!   exempt=  s   r  c                   @   sJ   e Zd ZU ejZeej ed< ej	Z
eej	 ed< ejZeej ed< dS )ConfigTypes
config_clsr  r  N)r/   r.   r0   r   Configr  r   r2   r   r	  r  
ArgFactoryr  r   r   r   r!   r  \  s   
 r  F)-experimental_allow_dataclass_attribute_accessexperimental_allow_control_flowexperimental_always_inlineexperimental_exemption_policyexperimental_config_types*experimental_result_must_contain_buildabler  r  r  r  r  c                   sh   du rddu rt jfddddd ddd fd	d
}| du r0|S || S )a  Rewrites the given function to make it generate a ``Config``.

  This function creates a new function from ``fn`` by rewriting its AST
  (abstract syntax tree), replacing all ``Call`` nodes with a custom call
  handler. When the rewritten function is run, the call handler intercepts calls
  and applies the following rules:

  - Calls to builtins, methods, callables without an inferrable signature,
    callables wrapped by `auto_config.exempt`, or other functions that have
    been ``auto_config``'ed take place as usual.
  - Calls to ``functools.partial`` are replaced by calling ``fdl.Partial`` with
    the same arguments;
  - All other calls are replaced by calling ``fdl.Config`` with the arguments
    that would have been passed to the called function or class.

  This function may be used standalone or as a decorator. The returned function
  is simply a wrapper around ``fn``, but with an additional ``as_buildable``
  attribute containing the rewritten function. For example::

    def build_model():
      return Sequential([
          Dense(num_units=128, activation=relu),
          Dense(num_units=128, activation=relu),
          Dense(num_units=1, activation=None),
      ])

    config = auto_config(build_model).as_buildable()

  The resulting ``config`` is equivalent to the following "manually" constructed
  configuration graph::

    fdl.Config(Sequential, layers=[
        fdl.Config(Dense, num_units=128, activation=relu),
        fdl.Config(Dense, num_units=128, activation=relu),
        fdl.Config(Dense, num_units=1, activation=None),
    ])

  This can then be built with ``fdl.build(config)``. Without modification, this
  will result in the same model as just calling ``build_model()`` directly.
  However, ``config`` permits changes to the model hyperparameters, for
  example::

      config.layers[0].num_units = 64
      config.layers[0].activation = 'elu'
      config.layers[1].num_units = 64
      config.layers[1].activation = 'elu'

      modified_model = fdl.build(config)

  Currently, control flow is not supported by default in ``auto_config``.
  Experimental support for control flow can be enabled using the
  ``experimental_allow_control_flow`` argument. If enabled, control flow
  constructs may be used within the function to construct the resulting config
  (for example, a ``for`` loop could be used to build a list of layers). Control
  flow is never encoded directly as part of the resulting ``fdl.Config`` (for
  example, there is no ``fdl.Config`` that will correspond to a conditional or
  loop). While many simple constructs (``for _ in range(10)`` etc) work, there
  will also likely be surprising behavior in some circumstances (for example,
  using ``itertools`` functions in conjunction with a loop will not work, since
  the calls to ``itertools`` functions will be turned into ``fdl.Config``
  objects).

  Using ``@auto_config`` is compatible with both ``@staticmethod`` and
  ``@classmethod``, however the ``@auto_config`` decorator must appear above the
  ``@classmethod`` or ``@staticmethod`` in the decorator list.

  Args:
    fn: The function to create a config-generating function from.
    experimental_allow_dataclass_attribute_access: Whether to allow attribute
      access on dataclasses within auto_config. Note that access to dataclass
      attribute is transformed into access to fdl.Config attributes in the
      as_buildable path.
    experimental_allow_control_flow: Whether to allow control flow constructs in
      ``fn``. By default, control flow constructs will cause an
      ``UnsupportedLanguageConstructError`` to be thrown.
    experimental_always_inline: If true, this function (when called in an
      ``auto_config`` context) will always be ``inline``'d in-place. See the
      documentation on ``inline`` for an example. The default (if unspecified)
      is currently ``False``, but this may change in the future.
    experimental_exemption_policy: An optional policy to control which function
      calls within the body of ``fn`` should be turned into ``fdl.Config``'s and
      which ones should simply be executed normally during the ``as_buildable``
      interpretation of ``fn``. This predicate should return ``True`` if the
      given callable should be exempted from auto-configuration.
    experimental_config_types: A ``ConfigTypes`` instance containing the types
      to use when generating configs. By default, this just supplies the
      standard Fiddle types ()``fdl.Config``, ``fdl.Partial``, and
      ``fdl.ArgFactory``), but projects with custom subclasses can use this to
      override the default. This is experimental and may be removed in the
      future.
    experimental_result_must_contain_buildable: If true, then raise an error if
      `fn.as_buildable` returns a result that does not contain any `Buildable`
      values -- e.g., if it returns an empty dict.

  Returns:
    A wrapped version of ``fn``, but with an additional ``as_buildable``
    attribute containing the rewritten function.
  NTc                    s   t | tr| jr| j|i |S j}| tju r*t||d g|dd R i |S | tju rTj	 t||d g fdd|dd D R i  fdd|
 D S | tu r_| |i |S | rj| |i |S j| g|R i |S )ay  Handles calls in auto_config'ed functions.

    This intercepts calls in an auto-configed function, and determines whether
    the called `fn_or_cls` should be wrapped in a `Config` or `Partial`. If
    `fn_or_cls` is `functools.partial`, the call will instead be converted into
    a call to Fiddle's `Partial`. If it is "auto-config eligible" (see
    `experimental_custom_call_policy`), then a `Config` will be create for
    `fn_or_cls` with the provided arguments. Otherwise, `fn_or_cls` is called
    directly.

    Args:
      fn_or_cls: The function or class being called.
      *args: The positional arguments with which `fn_or_cls` is being called.
      **kwargs: The keyword arguments with which `fn_or_cls` is being called.

    Returns:
      Depending on `fn_or_cls`, either `Partial`, a `Config`, or the result of
      calling `fn_or_cls` with the provided arguments.
    r   rb   Nc                    s   g | ]}t  |qS r   r  r   r  r   r!   r"     r   zAauto_config.<locals>.auto_config_call_handler.<locals>.<listcomp>c                    s   i | ]
\}}|t  |qS r   r  )r   r7   r   r   r   r!   
<dictcomp>  s    
zAauto_config.<locals>.auto_config_call_handler.<locals>.<dictcomp>)ru   r&   r)   rC   r  	functoolsr   r  r   r  itemsr  r  )r  r   r?   r  )r  r  r   r!   auto_config_call_handler  s,   
$


z-auto_config.<locals>.auto_config_call_handlerc                 S   sN   t | tjr"| j}|rt|rt| |S td|dt|  dt| |S )z5Handles attribute access in auto_config'ed functions.zCannot load attribute  on object of type p within auto_config, as this could lead to inconsistent behavior between the Python and as_buildable code paths.)	ru   r   rN   __fn_or_cls__dataclassesis_dataclassr3   r~   ri   )r8   ry   allow_dataclassr  r   r   r!   auto_config_attr_load_handler  s   

z2auto_config.<locals>.auto_config_attr_load_handlerc                 S   sN   t | tjr%| j}|rt|rt| || dS td|dt|  ddS )z6Handles saving attributes in auto_config'ed functions.NzCannot save attribute r%  r&  )	ru   r   rN   r'  r(  r)  setattrr~   ri   )rF   ry   r8   r*  r  r   r   r!   auto_config_attr_save_handler  s   z2auto_config.<locals>.auto_config_attr_save_handlerc                    s  t tjttfstddtdt ttfr%t}jnd }t}t	
}jj}t|||d}t|}||}t|}t||d }t |tjs[J t|}t|t	
d}t|}tjpsd}g }	tftffD ]\}
}|
|jv r|j|
}ttj|d}|	 ||f qt!|jv r|jt!}t}|	 ||f t"|	D ]
\}}|#|| qt$|}tj|j%|d	 j& _&j' _'rt(  fd
d}n }|r|||}t)|dS )Nz]`auto_config` is only compatible with functions, `@classmethod`s, and `@staticmethod`s.  Got z with type rs   )rT   rU   rV   r^   rb   execr   )r*  )closurec                     s8    | i |}t |stdj dt|j d|S )Nz(The `auto_config` rewritten version of `z` returned a `z`, which is not (or did not contain) a `fdl.Buildable`. Please ensure this function returns the result of an `auto_config`-eligible call expression, or a supported container (list, tuple, dict) containing one.)r   	TypeErrorr0   ri   r/   )r   r?   outputauto_config_fnr   r   r!   rC   u  s   
z;auto_config.<locals>.make_auto_config.<locals>.as_buildable)r)   )*ru   r  FunctionTypero   rp   r~   ri   __func__
_getsourcer   getsourcefiler   co_firstlinenorS   rv   parser   r   increment_linenor   r   compiler   listr  r   r   r   r}   r  r"  r   r|   r   sortedr   tuple__globals____defaults____kwdefaults__wrapsr&   )r   method_typerT   rU   rV   node_transformerr`   r   r/  indexed_handlers
handler_idhandlerhandler_idxrC   )r+  r-  r$  r  r  r  r  r2  r!   make_auto_config  s   







z%auto_config.<locals>.make_auto_config)T)r   latest)r   r  r  r  r  r  r  rI  r   )	r+  r-  r$  r  r  r  r  r  r  r!   rn   c  s   l
-
qrn   r  c                   s2    du rd dt f fdd}| du r|S || S )a>  Converts functions that create buildables directly into auto_config form.

  While most of the time, the benefits of an auto_config representation of
  object configuration and construction are valuable (e.g. static type
  checking and tooling / refactoring support), sometimes it is more convenient
  to manipulate buildable objects directly. ``auto_unconfig`` converts a
  function that directly manipulates ``fdl.Buildable``'s (e.g. ``fdl.Config``'s)
  into one that looks identically to an ``auto_config``'d function, and is fully
  interoperable with the rest of the ``auto_config`` ecosystem.

  Example::

    @auto_unconfig
    def make_experiment_trainer(name: str) -> fdl.Config[MyTrainer]
      model = make_model.as_buildable(name)
      select(model, DropOut).set(rate=0.42)  # Full Fiddle API available!
      dataset = make_dataset.as_buildable()
      # Build fdl.Config's imperatively.
      trainer_config = fdl.Config(MyTrainer)
      trainer_config.model = model
      trainer_config.train_dataset = dataset
      trainer_config.skip_eval = True
      return trainer_config  # Return a `fdl.Buildable`

    # Sample usage within an auto_config'd function.
    @auto_config
    def make_driver():
      return TrainerDriver(
        trainer=make_experiment_trainer('my_experiment'),
        checkpointer=CustomCheckpointer())

    # Sample usage outside of auto_config contexts.
    def main():
      # Use instantiated objects:
      trainer = make_experiment_trainer('my_experiment')
      for example in trainer.train_dataset:
        print_prediction(trainer.model.predict(example))

      # Or manipulate the configuration before calling `fdl.build`:
      trainer_config = make_experiment_trainer.as_buildable('my_experiment')
      trainer_config.skip_eval = False  # Tweak configuration.
      trainer2 = fdl.build(trainer_config)
      run_trainer(trainer2)

  Args:
    fn: The function to convert.
    experimental_always_inline: Whether the output of ``fn`` should always be
      inlined into the caller's config.

  Returns:
    An ``AutoConfig`` that corresponds to ``fn``.
  NTr:   c                    s$   t   fdd}t| dS )Nc                     s>   t jj}dt j_z | i |}t |W |t j_S |t j_w )NF)r	   _statein_buildbuild)r   r?   previouscfgr   r   r!   python_implementation  s   
zCauto_unconfig.<locals>.make_unconfig.<locals>.python_implementationrD   )r"  rB  r&   )r   rR  rK  rQ  r!   make_unconfig  s   	z$auto_unconfig.<locals>.make_unconfigr  )r   r  rS  r   rK  r!   auto_unconfig  s   8rT  function_objectc                 C   s
   t | tS r<   )ru   r&   )rU  r   r   r!   is_auto_config  r   rV  	buildablec                 C   s   t | tjstdt|  dt| jstd| j dtt| j}|j	di | j
}t |tjs7tdtj|| d dS )	a  Converts an ``auto_config``-based ``buildable`` into a DAG of Buildables.

  ``inline`` updates ``buildable`` in place to preserve aliasing within a larger
  Fiddle configuration. If you would like to leave ``buildable`` unmodified,
  make a shallow copy (``copy.copy``) before calling ``inline``.

  Example::

    # shared/input_pipelines.py
    @auto_config(experimental_always_inline=False)
    def make_input_pipeline(name: str, batch_size: int) -> InputPipeline:
      file_path = '/base_path/'+name
      augmentation = 'my_augmentation_routine'
      # ...
      return InputPipeline(file_path, augmentation, ...)

    # config/main.py
    @auto_config
    def make_experiment():
      data = make_input_pipeline('normal_dataset', batch_size)
      model = ...
      return Experiment(data, model)

    # experiment_configuration.py
    def make_experiment():
      config = make_experiment.as_buildable()
      config.data.name = 'advanced_dataset'
      # config.data.augmentation = 'custom_augmentation'  # Not configurable!!!
      # return fdl.build(config)                          # Works like normal.
      auto_config.inline(config.data)
      print(config.data.file_path)       # Prints: '/base_path/advanced_dataset'
      config.data.augmentation = 'custom_augmentation'    # Now exposed.
      experiment = fdl.build(config)                      # Works like normal.
      return experiment

  Args:
    buildable: The buildable of an ``auto_config``'d function to replace with
      the root of a Fiddle DAG that corresponds to it.

  Raises:
    ValueError: If ``buildable`` is not a ``Config``, or if ``buildable``
      doesn't correspond to an ``auto_config``'d function.
  z'Cannot `inline` non-Config buildables; z is not compatible.z-Cannot `inline` a non-auto_config function; `z` is not compatible.zJYou cannot currently inline functions that do not return `fdl.Buildable`s.)rT   destinationNr   )ru   r   r  r~   ri   rV  r'  r   r&   rC   __arguments__rN   r   move_buildable_internals)rW  r3  
tmp_configr   r   r!   inline  s*   ,

r\  c                 C   s    t | rt| S tt| S )z*Returns the source code for callable `fn`.)
_is_lambda_getsource_for_lambdatextwrapdedentr   	getsourcerQ  r   r   r!   r6  .  s   r6  c                 C   s<   t | sdS t| drt| dsdS | jdkp| jjdkS )z*Returns True if `fn` is a lambda function.Fr/   r   z<lambda>)r   
isfunctionr  r/   r   r   rQ  r   r   r!   r]  9  s
   
r]  c                       s4   e Zd ZdZejjfZ fddZdddZ	  Z
S )	_LambdaFinderzFCST Visitor that searches for the source code for a given lambda func.c                    s$   t    || _|jj| _g | _d S r<   )rI   r_   	lambda_fnr   r8  rc   
candidates)r,   rd  rK   r   r!   r_   G  s   


z_LambdaFinder.__init__r:   Nc                 C   s2   |  tjj|}|jj| jkr| j| d S d S r<   )	get_metadatacstmetadataPositionProviderstartre   rc   re  r|   )r,   r`   locr   r   r!   r   M  s   z_LambdaFinder.visit_Lambda)r:   N)r/   r.   r0   r1   rg  rh  ri  METADATA_DEPENDENCIESr_   r   rQ   r   r   rK   r!   rc  B  s
    
rc  c                 C   s   t | }t | }t||j}d|}t|}t	| }tj
|| t|jdkr<|jd }tj|gdjS |jsKtd|  d|j dtd|  d|j d	)
z2Returns source code for the given lambda function. rb   r   )r   z:Fiddle auto_config was unable to find the source code for z : could not find lambda on line rs   z!: multiple lambdas found on line z); try moving each lambda to its own line.)r   	getmoduler7  	linecachegetlines__dict__joinrg  parse_modulerc  rh  MetadataWrapperr   r   re  r   r   r~   rc   )r   r   rU   linesrT   
module_cstlambda_finderlambda_noder   r   r!   r^  S  s2   




r^  r(   c                    s    fdd}|S )z4A decorator that adds an auto_config-only code path.c                    s   t |  ddS )NTrD   r  r=   rB   r   r!   r   y  s   z&with_buildable_func.<locals>.decoratorr   )r(   r   r   rB   r!   with_buildable_funct  s   ry  r<   )Jr1   rv   builtinsr(  r"  r   ro  r_  r  typingr   r   r   r   r   r   fiddle._srcr   r	   r
   r
  r   r   r   r   fiddle._src.experimentalr   r   libcstrg  r   r   r   r   r   	argumentsr   	frozensetrq  values	_BUILTINSr#   	dataclassr&   SyntaxErrorrR   NodeTransformerrS   r   r   r4  r   CodeTyper   r   r   r  r  r  r  r  rO   Policyrn   rT  rV  r  r\  r6  r]  
CSTVisitorrc  r^  ry  r   r   r   r!   <module>   s   	 

; w
E+
	
  3
SD	!

