Rename 'macro' nomenclature to 'partial eval'.

pull/1/head
Stella Laurenzo 2020-06-26 13:50:51 -07:00
parent dd6a4e638b
commit e45287d83e
4 changed files with 137 additions and 115 deletions

View File

@ -17,13 +17,13 @@ from .target import *
__all__ = [ __all__ = [
"BuiltinsValueCoder", "BuiltinsValueCoder",
"Environment", "Environment",
"MacroEvalResult", "LiveValueRef",
"MacroEvalType",
"MacroResolver",
"MacroValueRef",
"NameReference", "NameReference",
"NameResolver", "NameResolver",
"ResolveAttrMacroValueRef", "PartialEvalResult",
"PartialEvalType",
"PartialEvalHook",
"ResolveAttrLiveValueRef",
"ValueCoder", "ValueCoder",
"ValueCoderChain", "ValueCoderChain",
] ]
@ -59,7 +59,7 @@ class NameReference:
self.name = name self.name = name
def load(self, env: "Environment", def load(self, env: "Environment",
ir_h: ir.DialectHelper) -> "MacroEvalResult": ir_h: ir.DialectHelper) -> "PartialEvalResult":
"""Loads the IR Value associated with the name. """Loads the IR Value associated with the name.
The load may either be direct, returning an existing value or The load may either be direct, returning an existing value or
@ -68,9 +68,9 @@ class NameReference:
Args: Args:
ir_h: The dialect helper used to emit code. ir_h: The dialect helper used to emit code.
Returns: Returns:
A macro evaluation result. A partial evaluation result.
""" """
return MacroEvalResult.not_evaluated() return PartialEvalResult.not_evaluated()
def store(self, env: "Environment", value: ir.Value, ir_h: ir.DialectHelper): def store(self, env: "Environment", value: ir.Value, ir_h: ir.DialectHelper):
"""Stores a new value into the name. """Stores a new value into the name.
@ -103,57 +103,64 @@ class NameResolver:
################################################################################ ################################################################################
# Macro evaluation # Partial evaluation
# When the compiler is extracting from a running program, it is likely that # When the compiler is extracting from a running program, it is likely that
# evaluations produce live values which can be further partially evaluated # evaluations produce live values which can be further partially evaluated
# at import time, in the context of the running instance (versus emitting # at import time, in the context of the running instance (versus emitting
# program IR to do so). This facility is called macro evaluation and is # program IR to do so). This behavior is controlled through a PartialEvalHook
# a pluggable component on the environment. # on the environment.
################################################################################ ################################################################################
class MacroEvalType(Enum): class PartialEvalType(Enum):
# The macro could not be evaluated immediately and the operation should # Could not be evaluated immediately and the operation should be
# be code-generated. yields NotImplemented. # code-generated. yields NotImplemented.
NOT_EVALUATED = 0 NOT_EVALUATED = 0
# The macro yields a LiveValueRef # Yields a LiveValueRef
YIELDS_LIVE_VALUE = 1 YIELDS_LIVE_VALUE = 1
# The macro yields an IR value # Yields an IR value
YIELDS_IR_VALUE = 2 YIELDS_IR_VALUE = 2
# Evaluation yielded an error (yields contains exc_info from sys.exc_info()). # Evaluation yielded an error (yields contains exc_info from sys.exc_info()).
ERROR = 3 ERROR = 3
class MacroEvalResult(namedtuple("MacroEvalResult", "type,yields")): class PartialEvalResult(namedtuple("PartialEvalResult", "type,yields")):
"""Encapsulates the result of a macro evaluation.""" """Encapsulates the result of a partial evaluation."""
@classmethod @classmethod
def not_evaluated(cls): def not_evaluated(cls):
return cls(MacroEvalType.NOT_EVALUATED, NotImplemented) return cls(PartialEvalType.NOT_EVALUATED, NotImplemented)
@classmethod @classmethod
def yields_live_value(cls, live_value): def yields_live_value(cls, live_value):
assert isinstance(live_value, MacroValueRef) assert isinstance(live_value, LiveValueRef)
return cls(MacroEvalType.YIELDS_LIVE_VALUE, live_value) return cls(PartialEvalType.YIELDS_LIVE_VALUE, live_value)
@classmethod @classmethod
def yields_ir_value(cls, ir_value): def yields_ir_value(cls, ir_value):
assert isinstance(ir_value, ir.Value) assert isinstance(ir_value, ir.Value)
return cls(MacroEvalType.YIELDS_IR_VALUE, ir_value) return cls(PartialEvalType.YIELDS_IR_VALUE, ir_value)
@classmethod @classmethod
def error(cls): def error(cls):
return cls(MacroEvalType.ERROR, sys.exc_info()) return cls(PartialEvalType.ERROR, sys.exc_info())
@classmethod
def error_message(cls, message):
try:
raise RuntimeError(message)
except RuntimeError:
return cls.error()
class MacroValueRef: class LiveValueRef:
"""Wraps a live value from the containing environment. """Wraps a live value from the containing environment.
Typically, when expressions encounter a live value, a limited number of Typically, when expressions encounter a live value, a limited number of
"macro" expansions can be done against it in place (versus emitting the code partial evaluations can be done against it in place (versus emitting the code
to import it and perform the operation). This default base class will not to import it and perform the operation). This default base class will not
perform any static evaluations. perform any static evaluations.
""" """
@ -165,31 +172,31 @@ class MacroValueRef:
super().__init__() super().__init__()
self.live_value = live_value self.live_value = live_value
def resolve_getattr(self, env: "Environment", attr_name) -> MacroEvalResult: def resolve_getattr(self, env: "Environment", attr_name) -> PartialEvalResult:
"""Gets a named attribute from the live value.""" """Gets a named attribute from the live value."""
return MacroEvalResult.not_evaluated() return PartialEvalResult.not_evaluated()
def __repr__(self): def __repr__(self):
return "MacroValueRef({}, {})".format(self.__class__.__name__, return "MacroValueRef({}, {})".format(self.__class__.__name__,
self.live_value) self.live_value)
class ResolveAttrMacroValueRef(MacroValueRef): class ResolveAttrLiveValueRef(LiveValueRef):
"""Custom MacroValueRef that will resolve attributes via getattr.""" """Custom MacroValueRef that will resolve attributes via getattr."""
__slots__ = [] __slots__ = []
def resolve_getattr(self, env: "Environment", attr_name) -> MacroEvalResult: def resolve_getattr(self, env: "Environment", attr_name) -> PartialEvalResult:
logging.debug("RESOLVE_GETATTR '{}' on {}".format(attr_name, logging.debug("RESOLVE_GETATTR '{}' on {}".format(attr_name,
self.live_value)) self.live_value))
try: try:
attr_py_value = getattr(self.live_value, attr_name) attr_py_value = getattr(self.live_value, attr_name)
except: except:
return MacroEvalResult.error() return PartialEvalResult.error()
return env.macro_resolver.resolve(attr_py_value) return env.partial_eval_hook.resolve(attr_py_value)
class MacroResolver: class PartialEvalHook:
"""Owned by an environment and performs system-wide macro resolution.""" """Owned by an environment to customize partial evaluation."""
__slots__ = [ __slots__ = [
"_value_map", "_value_map",
] ]
@ -198,26 +205,26 @@ class MacroResolver:
super().__init__() super().__init__()
self._value_map = PyValueMap() self._value_map = PyValueMap()
def resolve(self, py_value) -> MacroEvalResult: def resolve(self, py_value) -> PartialEvalResult:
"""Performs macro resolution on a python value.""" """Performs partial evaluation on a python value."""
binding = self._value_map.lookup(py_value) binding = self._value_map.lookup(py_value)
if binding is None: if binding is None:
logging.debug("MACRO RESOLVE {}: Passthrough", py_value) logging.debug("PARTIAL EVAL RESOLVE {}: Passthrough", py_value)
return MacroEvalResult.yields_live_value(MacroValueRef(py_value)) return PartialEvalResult.yields_live_value(LiveValueRef(py_value))
if isinstance(binding, MacroValueRef): if isinstance(binding, LiveValueRef):
logging.debug("MACRO RESOLVE {}: {}", py_value, binding) logging.debug("PARTIAL EVAL RESOLVE {}: {}", py_value, binding)
return MacroEvalResult.yields_live_value(binding) return PartialEvalResult.yields_live_value(binding)
if isinstance(binding, MacroEvalResult): if isinstance(binding, PartialEvalResult):
return binding return binding
# Attempt to call. # Attempt to call.
try: try:
binding = binding(py_value) binding = binding(py_value)
assert isinstance(binding, MacroEvalResult), ( assert isinstance(binding, PartialEvalResult), (
"Expected MacroEvalResult but got {}".format(binding)) "Expected PartialEvalResult but got {}".format(binding))
logging.debug("MACRO RESOLVE {}: {}", py_value, binding) logging.debug("PARTIAL EVAL RESOLVE {}: {}", py_value, binding)
return binding return binding
except: except:
return MacroEvalResult.error() return PartialEvalResult.error()
def _bind(self, def _bind(self,
binding, binding,
@ -236,10 +243,10 @@ class MacroResolver:
"Must specify one of 'for_ref', 'for_type' or 'for_predicate") "Must specify one of 'for_ref', 'for_type' or 'for_predicate")
def enable_getattr(self, **kwargs): def enable_getattr(self, **kwargs):
"""Enables macro attribute resolution.""" """Enables partial evaluation of getattr."""
self._bind( self._bind(
lambda pv: MacroEvalResult.yields_live_value( lambda pv: PartialEvalResult.yields_live_value(
ResolveAttrMacroValueRef(pv)), **kwargs) ResolveAttrLiveValueRef(pv)), **kwargs)
################################################################################ ################################################################################
@ -258,7 +265,7 @@ class Environment(NameResolver):
"_name_resolvers", "_name_resolvers",
"target", "target",
"value_coder", "value_coder",
"macro_resolver", "partial_eval_hook",
] ]
def __init__(self, def __init__(self,
@ -267,13 +274,14 @@ class Environment(NameResolver):
target: Target, target: Target,
name_resolvers=(), name_resolvers=(),
value_coder, value_coder,
macro_resolver=None): partial_eval_hook=None):
super().__init__() super().__init__()
self.ir_h = ir_h self.ir_h = ir_h
self.target = target self.target = target
self._name_resolvers = name_resolvers self._name_resolvers = name_resolvers
self.value_coder = value_coder self.value_coder = value_coder
self.macro_resolver = macro_resolver if macro_resolver else MacroResolver() self.partial_eval_hook = partial_eval_hook if partial_eval_hook else PartialEvalHook(
)
@classmethod @classmethod
def for_const_global_function(cls, ir_h: ir.DialectHelper, f, *, def for_const_global_function(cls, ir_h: ir.DialectHelper, f, *,
@ -332,12 +340,11 @@ class LocalNameReference(NameReference):
super().__init__(name) super().__init__(name)
self._current_value = initial_value self._current_value = initial_value
def load(self, env: "Environment") -> MacroEvalResult: def load(self, env: "Environment") -> PartialEvalResult:
if self._current_value is None: if self._current_value is None:
return MacroEvalResult.error( return PartialEvalResult.error_message(
RuntimeError("Attempt to access local '{}' before assignment".format( "Attempt to access local '{}' before assignment".format(self.name))
self.name))) return PartialEvalResult.yields_ir_value(self._current_value)
return MacroEvalResult.yields_ir_value(self._current_value)
def store(self, env: "Environment", value: ir.Value): def store(self, env: "Environment", value: ir.Value):
self._current_value = value self._current_value = value
@ -374,8 +381,8 @@ class ConstNameReference(NameReference):
super().__init__(name) super().__init__(name)
self._py_value = py_value self._py_value = py_value
def load(self, env: "Environment") -> MacroEvalResult: def load(self, env: "Environment") -> PartialEvalResult:
return env.macro_resolver.resolve(self._py_value) return env.partial_eval_hook.resolve(self._py_value)
def __repr__(self): def __repr__(self):
return "<ConstNameReference({}={})>".format(self.name, self._py_value) return "<ConstNameReference({}={})>".format(self.name, self._py_value)

View File

@ -31,7 +31,7 @@ class ImportFrontend:
"_helper", "_helper",
"_target_factory", "_target_factory",
"_value_coder", "_value_coder",
"_macro_resolver", "_partial_eval_hook",
] ]
def __init__(self, def __init__(self,
@ -39,15 +39,15 @@ class ImportFrontend:
*, *,
target_factory: TargetFactory = GenericTarget64, target_factory: TargetFactory = GenericTarget64,
value_coder: Optional[ValueCoder] = None, value_coder: Optional[ValueCoder] = None,
macro_resolver: Optional[MacroResolver] = None): partial_eval_hook: Optional[PartialEvalHook] = None):
self._ir_context = ir.MLIRContext() if not ir_context else ir_context self._ir_context = ir.MLIRContext() if not ir_context else ir_context
self._ir_module = self._ir_context.new_module() self._ir_module = self._ir_context.new_module()
self._helper = AllDialectHelper(self._ir_context, self._helper = AllDialectHelper(self._ir_context,
ir.OpBuilder(self._ir_context)) ir.OpBuilder(self._ir_context))
self._target_factory = target_factory self._target_factory = target_factory
self._value_coder = value_coder if value_coder else BuiltinsValueCoder() self._value_coder = value_coder if value_coder else BuiltinsValueCoder()
self._macro_resolver = (macro_resolver if macro_resolver else self._partial_eval_hook = (partial_eval_hook if partial_eval_hook else
build_default_macro_resolver()) build_default_partial_eval_hook())
@property @property
def ir_context(self): def ir_context(self):
@ -62,8 +62,8 @@ class ImportFrontend:
return self._helper return self._helper
@property @property
def macro_resolver(self): def partial_eval_hook(self):
return self._macro_resolver return self._partial_eval_hook
def import_global_function(self, f): def import_global_function(self, f):
"""Imports a global function. """Imports a global function.
@ -121,7 +121,7 @@ class ImportFrontend:
parameter_bindings=zip(f_params.keys(), ir_f.first_block.args), parameter_bindings=zip(f_params.keys(), ir_f.first_block.args),
value_coder=self._value_coder, value_coder=self._value_coder,
target=target, target=target,
macro_resolver=self._macro_resolver) partial_eval_hook=self._partial_eval_hook)
fctx = FunctionContext(ir_c=ir_c, fctx = FunctionContext(ir_c=ir_c,
ir_f=ir_f, ir_f=ir_f,
ir_h=h, ir_h=h,
@ -166,8 +166,8 @@ class AllDialectHelper(Numpy.DialectHelper, ScfDialectHelper):
ScfDialectHelper.__init__(self, *args, **kwargs) ScfDialectHelper.__init__(self, *args, **kwargs)
def build_default_macro_resolver() -> MacroResolver: def build_default_partial_eval_hook() -> PartialEvalHook:
mr = MacroResolver() mr = PartialEvalHook()
### Modules ### Modules
mr.enable_getattr(for_type=ast.__class__) # The module we use is arbitrary. mr.enable_getattr(for_type=ast.__class__) # The module we use is arbitrary.

View File

@ -46,16 +46,16 @@ class FunctionContext:
ir.emit_error(loc, message) ir.emit_error(loc, message)
raise EmittedError(loc, message) raise EmittedError(loc, message)
def check_macro_evaluated(self, result: MacroEvalResult): def check_partial_evaluated(self, result: PartialEvalResult):
"""Checks that a macro has evaluated without error.""" """Checks that a PartialEvalResult has evaluated without error."""
if result.type == MacroEvalType.ERROR: if result.type == PartialEvalType.ERROR:
exc_info = result.yields exc_info = result.yields
loc = self.current_loc loc = self.current_loc
message = ("Error while evaluating value from environment:\n" + message = ("Error while evaluating value from environment:\n" +
"".join(traceback.format_exception(*exc_info))) "".join(traceback.format_exception(*exc_info)))
ir.emit_error(loc, message) ir.emit_error(loc, message)
raise EmittedError(loc, message) raise EmittedError(loc, message)
if result.type == MacroEvalType.NOT_EVALUATED: if result.type == PartialEvalType.NOT_EVALUATED:
self.abort("Unable to evaluate expression") self.abort("Unable to evaluate expression")
@property @property
@ -82,22 +82,26 @@ class FunctionContext:
self.abort("Cannot code python value as constant: {}".format(py_value)) self.abort("Cannot code python value as constant: {}".format(py_value))
return result return result
def emit_macro_result(self, macro_result: MacroEvalResult) -> ir.Value: def emit_partial_eval_result(self,
"""Emits a macro result either as a direct IR value or a constant.""" partial_result: PartialEvalResult) -> ir.Value:
self.check_macro_evaluated(macro_result) """Emits a partial eval result either as a direct IR value or a constant."""
if macro_result.type == MacroEvalType.YIELDS_IR_VALUE: self.check_partial_evaluated(partial_result)
if partial_result.type == PartialEvalType.YIELDS_IR_VALUE:
# Return directly. # Return directly.
return macro_result.yields return partial_result.yields
elif macro_result.type == MacroEvalType.YIELDS_LIVE_VALUE: elif partial_result.type == PartialEvalType.YIELDS_LIVE_VALUE:
# Import constant. # Import constant.
return self.emit_const_value(macro_result.yields.live_value) return self.emit_const_value(partial_result.yields.live_value)
else: else:
self.abort("Unhandled macro result type {}".format(macro_result)) self.abort("Unhandled partial eval result type {}".format(partial_result))
class BaseNodeVisitor(ast.NodeVisitor): class BaseNodeVisitor(ast.NodeVisitor):
"""Base class of a node visitor that aborts on unhandled nodes.""" """Base class of a node visitor that aborts on unhandled nodes."""
IMPORTER_TYPE = "<unknown>" IMPORTER_TYPE = "<unknown>"
__slots__ = [
"fctx",
]
def __init__(self, fctx): def __init__(self, fctx):
super().__init__() super().__init__()
@ -119,6 +123,10 @@ class FunctionDefImporter(BaseNodeVisitor):
Handles nodes that are direct children of a FunctionDef. Handles nodes that are direct children of a FunctionDef.
""" """
IMPORTER_TYPE = "statement" IMPORTER_TYPE = "statement"
__slots__ = [
"ast_fd",
"_last_was_return",
]
def __init__(self, fctx, ast_fd): def __init__(self, fctx, ast_fd):
super().__init__(fctx) super().__init__(fctx)
@ -186,6 +194,9 @@ class ExpressionImporter(BaseNodeVisitor):
IR value that the expression lowers to. IR value that the expression lowers to.
""" """
IMPORTER_TYPE = "expression" IMPORTER_TYPE = "expression"
__slots__ = [
"value",
]
def __init__(self, fctx): def __init__(self, fctx):
super().__init__(fctx) super().__init__(fctx)
@ -209,12 +220,13 @@ class ExpressionImporter(BaseNodeVisitor):
self.value = ir_const_value self.value = ir_const_value
def visit_Attribute(self, ast_node): def visit_Attribute(self, ast_node):
# Import the attribute's value recursively as a macro if possible. # Import the attribute's value recursively as a partial eval if possible.
macro_importer = MacroImporter(self.fctx) pe_importer = PartialEvalImporter(self.fctx)
macro_importer.visit(ast_node) pe_importer.visit(ast_node)
if macro_importer.macro_result: if pe_importer.partial_eval_result:
self.fctx.check_macro_evaluated(macro_importer.macro_result) self.fctx.check_partial_evaluated(pe_importer.partial_eval_result)
self.value = self.fctx.emit_macro_result(macro_importer.macro_result) self.value = self.fctx.emit_partial_eval_result(
pe_importer.partial_eval_result)
return return
self.fctx.abort("unhandled attribute access mode: {}".format( self.fctx.abort("unhandled attribute access mode: {}".format(
@ -331,9 +343,9 @@ class ExpressionImporter(BaseNodeVisitor):
self.fctx.abort("Unsupported expression name context type %s" % self.fctx.abort("Unsupported expression name context type %s" %
ast_node.ctx.__class__.__name__) ast_node.ctx.__class__.__name__)
name_ref = self.fctx.lookup_name(ast_node.id) name_ref = self.fctx.lookup_name(ast_node.id)
macro_result = name_ref.load(self.fctx.environment) pe_result = name_ref.load(self.fctx.environment)
logging.debug("LOAD {} -> {}", name_ref, macro_result) logging.debug("LOAD {} -> {}", name_ref, pe_result)
self.value = self.fctx.emit_macro_result(macro_result) self.value = self.fctx.emit_partial_eval_result(pe_result)
def visit_UnaryOp(self, ast_node): def visit_UnaryOp(self, ast_node):
ir_h = self.fctx.ir_h ir_h = self.fctx.ir_h
@ -371,52 +383,55 @@ class ExpressionImporter(BaseNodeVisitor):
self.emit_constant(ast_node.value) self.emit_constant(ast_node.value)
class MacroImporter(BaseNodeVisitor): class PartialEvalImporter(BaseNodeVisitor):
"""Importer for expressions that can resolve through the environment's macro """Importer for performing greedy partial evaluation.
system.
Concretely this is used for Attribute.value and Call resolution. Concretely this is used for Attribute.value and Call resolution.
Attribute resolution is not just treated as a normal expression because it Attribute resolution is not just treated as a normal expression because it
is first subject to "macro expansion", allowing the environment's macro is first subject to "partial evaluation", allowing the environment's partial
resolution facility to operate on live python values from the containing eval hook to operate on live python values from the containing
environment versus naively emitting code for attribute resolution from environment versus naively emitting code for attribute resolution for
entities that can/should be considered constants from the hosting context. entities that can/should be considered constants from the hosting context.
This is used, for example, to resolve attributes from modules without This is used, for example, to resolve attributes from modules without
by immediately dereferencing/transforming the intervening chain of attributes. immediately dereferencing/transforming the intervening chain of attributes.
""" """
IMPORTER_TYPE = "macro" IMPORTER_TYPE = "partial_eval"
__slots__ = [
"partial_eval_result",
]
def __init__(self, fctx): def __init__(self, fctx):
super().__init__(fctx) super().__init__(fctx)
self.macro_result = None self.partial_eval_result = None
def visit_Attribute(self, ast_node): def visit_Attribute(self, ast_node):
# Sub-evaluate the 'value'. # Sub-evaluate the 'value'.
sub_macro = MacroImporter(self.fctx) sub_eval = PartialEvalImporter(self.fctx)
sub_macro.visit(ast_node.value) sub_eval.visit(ast_node.value)
if sub_macro.macro_result: if sub_eval.partial_eval_result:
# Macro sub-evaluation successful. # Partial sub-evaluation successful.
sub_result = sub_macro.macro_result sub_result = sub_eval.partial_eval_result
else: else:
# Need to evaluate it as an expression. # Need to evaluate it as an expression.
sub_expr = ExpressionImporter(self.fctx) sub_expr = ExpressionImporter(self.fctx)
sub_expr.visit(ast_node.value) sub_expr.visit(ast_node.value)
assert sub_expr.value, ( assert sub_expr.value, (
"Macro sub expression did not return a value: %r" % (ast_node.value)) "Evaluated sub expression did not return a value: %r" %
sub_result = MacroEvalResult.yields_ir_value(sub_expr.value) (ast_node.value))
sub_result = PartialEvalResult.yields_ir_value(sub_expr.value)
# Attempt to perform a static getattr as a macro if still operating on a # Attempt to perform a static getattr as a partial eval if still operating
# live value. # on a live value.
self.fctx.check_macro_evaluated(sub_result) self.fctx.check_partial_evaluated(sub_result)
if sub_result.type == MacroEvalType.YIELDS_LIVE_VALUE: if sub_result.type == PartialEvalType.YIELDS_LIVE_VALUE:
logging.debug("STATIC getattr '{}' on {}", ast_node.attr, sub_result) logging.debug("STATIC getattr '{}' on {}", ast_node.attr, sub_result)
getattr_result = sub_result.yields.resolve_getattr( getattr_result = sub_result.yields.resolve_getattr(
self.fctx.environment, ast_node.attr) self.fctx.environment, ast_node.attr)
if getattr_result.type != MacroEvalType.NOT_EVALUATED: if getattr_result.type != PartialEvalType.NOT_EVALUATED:
self.fctx.check_macro_evaluated(getattr_result) self.fctx.check_partial_evaluated(getattr_result)
self.macro_result = getattr_result self.partial_eval_result = getattr_result
return return
# If a non-statically evaluable live value, then convert to a constant # If a non-statically evaluable live value, then convert to a constant
# and dynamic dispatch. # and dynamic dispatch.
@ -424,7 +439,7 @@ class MacroImporter(BaseNodeVisitor):
else: else:
ir_value = sub_result.yields ir_value = sub_result.yields
# Yielding an IR value from a recursive macro evaluation means that the # Yielding an IR value from a recursive partial evaluation means that the
# entire chain needs to be hoisted to IR. # entire chain needs to be hoisted to IR.
# TODO: Implement. # TODO: Implement.
self.fctx.abort("dynamic-emitted getattr not yet supported: %r" % self.fctx.abort("dynamic-emitted getattr not yet supported: %r" %
@ -432,9 +447,9 @@ class MacroImporter(BaseNodeVisitor):
def visit_Name(self, ast_node): def visit_Name(self, ast_node):
name_ref = self.fctx.lookup_name(ast_node.id) name_ref = self.fctx.lookup_name(ast_node.id)
macro_result = name_ref.load(self.fctx.environment) partial_eval_result = name_ref.load(self.fctx.environment)
logging.debug("LOAD MACRO {} -> {}", name_ref, macro_result) logging.debug("PARTIAL EVAL {} -> {}", name_ref, partial_eval_result)
self.macro_result = macro_result self.partial_eval_result = partial_eval_result
class EmittedError(Exception): class EmittedError(Exception):