diff --git a/CHANGELOG.md b/CHANGELOG.md index 97222856a..b2ccf6745 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added + * Added a compiler metadata flag for suppressing warnings when Var indirection is unavoidable (#1052) ## [v0.2.2] ### Added diff --git a/docs/compiler.rst b/docs/compiler.rst index c06058112..eef20b7ee 100644 --- a/docs/compiler.rst +++ b/docs/compiler.rst @@ -127,6 +127,10 @@ The ``^:dynamic`` metadata key will force all accesses to the so-marked Var to b The ``^:redef`` metadata key can be used if you intend to re-``def`` a Var later and you need changes to be propagated. It is unlikely you will want to do this, but you can configure the compiler to emit all Var accesses with indirection using the ``use-var-indirection`` configuration option in :ref:`compiler_generation_configuration`. +There may be cases where direct linking is impossible, such as in Var references generated within macros when the compiler cannot verify the existence of the referenced name. +For such cases, the compiler will emit a warning if a direct link is not possible (if it is so configured). +You can suppress those warnings locally by attaching the ``^:no-warn-on-var-indirection`` metadata to the symbol in question. + .. note:: Changes to Vars which were direct linked will not be propagated to any code that used the direct link, rather than Var indirection. diff --git a/src/basilisp/core.lpy b/src/basilisp/core.lpy index 092cbe9b8..5b6ff72fe 100644 --- a/src/basilisp/core.lpy +++ b/src/basilisp/core.lpy @@ -6203,7 +6203,7 @@ :object-type obj-type#}))))) arglists)))) (let [dotted-method-name (symbol (str "." (name method-name)))] - `(.register ~method-name + `(.register ~(vary-meta method-name assoc :no-warn-on-var-indirection true) ~interface-name (fn ~method-name ~@(map (fn [[obj-sym & args]] @@ -6281,7 +6281,7 @@ :interface ~interface-sym :methods ~(apply hash-map (mapcat #(let [v (first %)] - [(keyword (name v)) v]) + [(keyword (name v)) (vary-meta v assoc :no-warn-on-var-indirection true)]) methods)) :var (var ~protocol-name)} (alter-var-root (var ~protocol-name) merge ~protocol-name))) @@ -7091,14 +7091,15 @@ ;;;;;;;;;;;;;;;;;;;;;;;;; (defmulti ^:private make-custom-data-readers - (fn [obj metadata] (type obj))) + (fn [obj ^:no-warn-when-unused metadata] + (type obj))) (defmethod make-custom-data-readers :default - [obj mta] - (throw (ex-info "Not a valid data-reader map" (assoc mta :object obj)))) + [obj metadata] + (throw (ex-info "Not a valid data-reader map" (assoc metadata :object obj)))) (defmethod make-custom-data-readers basilisp.lang.interfaces/IPersistentMap - [mappings mta] + [mappings metadata] (reduce (fn [m [k v]] (let [v' (if (qualified-symbol? v) (intern (create-ns (symbol (namespace v))) @@ -7108,33 +7109,33 @@ (not (qualified-symbol? k)) (throw (ex-info "Invalid tag in data-readers. Expected qualified symbol." - (merge mta {:form k}))) + (merge metadata {:form k}))) (not (ifn? v')) (throw (ex-info "Invalid reader function in data-readers" - (merge mta {:form v}))) + (merge metadata {:form v}))) :else - (assoc m (with-meta k mta) v')))) + (assoc m (with-meta k metadata) v')))) mappings mappings)) (defmethod make-custom-data-readers importlib.metadata/EntryPoint - [entry-point mta] + [entry-point metadata] (make-custom-data-readers (.load entry-point) - (assoc mta + (assoc metadata :basilisp.entry-point/name (.-name entry-point) :basilisp.entry-point/group (.-group entry-point)))) (defmethod make-custom-data-readers pathlib/Path - [file mta] + [file metadata] (make-custom-data-readers (with-open [rdr (basilisp.io/reader file)] (read (if (.endswith (name file) "cljc") {:eof nil :read-cond :allow} {:eof nil}) rdr)) - (assoc mta :file (str file)))) + (assoc metadata :file (str file)))) (defn- data-readers-entry-points [] (when (#{"true" "t" "1" "yes" "y"} (.lower diff --git a/src/basilisp/core/protocols.lpy b/src/basilisp/core/protocols.lpy index 5b02cc95f..4790cd5c1 100644 --- a/src/basilisp/core/protocols.lpy +++ b/src/basilisp/core/protocols.lpy @@ -6,9 +6,9 @@ (extend-protocol CollReduce nil - (coll-reduce [s f] + (coll-reduce [_s f] (f)) - (coll-reduce [s f init] + (coll-reduce [_s _f init] init) python/object @@ -29,7 +29,7 @@ (extend-protocol KVReduce nil - (kv-reduce [s f init] + (kv-reduce [_s _f init] init) python/object diff --git a/src/basilisp/edn.lpy b/src/basilisp/edn.lpy index 67c143d29..353cbc604 100644 --- a/src/basilisp/edn.lpy +++ b/src/basilisp/edn.lpy @@ -622,7 +622,7 @@ (.write writer "\"") nil) nil - (write* [this writer] + (write* [_this writer] (.write writer "nil") nil) diff --git a/src/basilisp/lang/compiler/analyzer.py b/src/basilisp/lang/compiler/analyzer.py index e02b83862..9105eb06f 100644 --- a/src/basilisp/lang/compiler/analyzer.py +++ b/src/basilisp/lang/compiler/analyzer.py @@ -71,6 +71,7 @@ SYM_NO_INLINE_META_KEY, SYM_NO_WARN_ON_REDEF_META_KEY, SYM_NO_WARN_ON_SHADOW_META_KEY, + SYM_NO_WARN_ON_VAR_INDIRECTION_META_KEY, SYM_NO_WARN_WHEN_UNUSED_META_KEY, SYM_PRIVATE_META_KEY, SYM_PROPERTY_META_KEY, @@ -674,6 +675,7 @@ def get_meta_prop(o: Union[IMeta, Var]) -> Any: _is_py_staticmethod = _bool_meta_getter(SYM_STATICMETHOD_META_KEY) _is_macro = _bool_meta_getter(SYM_MACRO_META_KEY) _is_no_inline = _bool_meta_getter(SYM_NO_INLINE_META_KEY) +_is_allow_var_indirection = _bool_meta_getter(SYM_NO_WARN_ON_VAR_INDIRECTION_META_KEY) _is_use_var_indirection = _bool_meta_getter(SYM_USE_VAR_INDIRECTION_KEY) _inline_meta = _meta_getter(SYM_INLINE_META_KW) _tag_meta = _meta_getter(SYM_TAG_META_KEY) @@ -3506,6 +3508,7 @@ def __resolve_namespaced_symbol_in_ns( return VarRef( form=form, var=v, + is_allow_var_indirection=_is_allow_var_indirection(form), env=ctx.get_node_env(pos=ctx.syntax_position), ) @@ -3525,6 +3528,7 @@ def __resolve_namespaced_symbol( # pylint: disable=too-many-branches # noqa: M return VarRef( form=form, var=v, + is_allow_var_indirection=_is_allow_var_indirection(form), env=ctx.get_node_env(pos=ctx.syntax_position), ) elif form.ns == _BUILTINS_NS: @@ -3549,7 +3553,12 @@ def __resolve_namespaced_symbol( # pylint: disable=too-many-branches # noqa: M f"cannot resolve private Var {form.name} from namespace {form.ns}", form=form, ) - return VarRef(form=form, var=v, env=ctx.get_node_env(pos=ctx.syntax_position)) + return VarRef( + form=form, + var=v, + is_allow_var_indirection=_is_allow_var_indirection(form), + env=ctx.get_node_env(pos=ctx.syntax_position), + ) if "." in form.name and form.name != _DOUBLE_DOT_MACRO_NAME: raise ctx.AnalyzerException( @@ -3610,6 +3619,7 @@ def __resolve_namespaced_symbol( # pylint: disable=too-many-branches # noqa: M target=VarRef( form=form, var=maybe_type_or_class, + is_allow_var_indirection=_is_allow_var_indirection(form), env=ctx.get_node_env(pos=ctx.syntax_position), ), is_assignable=False, @@ -3624,8 +3634,7 @@ def __resolve_namespaced_symbol( # pylint: disable=too-many-branches # noqa: M def __resolve_bare_symbol( ctx: AnalyzerContext, form: sym.Symbol ) -> Union[Const, MaybeClass, VarRef]: - """Resolve a non-namespaced symbol into a Python name or a local - Basilisp Var.""" + """Resolve a non-namespaced symbol into a Python name or a local Basilisp Var.""" assert form.ns is None # Look up the symbol in the namespace mapping of the current namespace. @@ -3635,6 +3644,7 @@ def __resolve_bare_symbol( return VarRef( form=form, var=v, + is_allow_var_indirection=_is_allow_var_indirection(form), env=ctx.get_node_env(pos=ctx.syntax_position), ) diff --git a/src/basilisp/lang/compiler/constants.py b/src/basilisp/lang/compiler/constants.py index 13e207fe1..5ac11fc75 100644 --- a/src/basilisp/lang/compiler/constants.py +++ b/src/basilisp/lang/compiler/constants.py @@ -47,6 +47,7 @@ class SpecialForm: SYM_MUTABLE_META_KEY = kw.keyword("mutable") SYM_NO_WARN_ON_REDEF_META_KEY = kw.keyword("no-warn-on-redef") SYM_NO_WARN_ON_SHADOW_META_KEY = kw.keyword("no-warn-on-shadow") +SYM_NO_WARN_ON_VAR_INDIRECTION_META_KEY = kw.keyword("no-warn-on-var-indirection") SYM_NO_WARN_WHEN_UNUSED_META_KEY = kw.keyword("no-warn-when-unused") SYM_REDEF_META_KEY = kw.keyword("redef") SYM_STATICMETHOD_META_KEY = kw.keyword("staticmethod") diff --git a/src/basilisp/lang/compiler/generator.py b/src/basilisp/lang/compiler/generator.py index e2d09e49c..bdd79da0c 100644 --- a/src/basilisp/lang/compiler/generator.py +++ b/src/basilisp/lang/compiler/generator.py @@ -257,7 +257,7 @@ def use_var_indirection(self) -> bool: @property def warn_on_var_indirection(self) -> bool: """If True, warn when a Var reference cannot be direct linked (iff - use_var_indirection is False)..""" + use_var_indirection is False).""" return not self.use_var_indirection and self._opts.val_at( WARN_ON_VAR_INDIRECTION, True ) @@ -3188,7 +3188,7 @@ def _var_sym_to_py_ast( if direct_link is not None: return direct_link - if ctx.warn_on_var_indirection: + if ctx.warn_on_var_indirection and not node.is_allow_var_indirection: logger.warning( f"could not resolve a direct link to Var '{var_name}' " f"({node.env.ns}:{node.env.line})" diff --git a/src/basilisp/lang/compiler/nodes.py b/src/basilisp/lang/compiler/nodes.py index 9aaf7beee..b23e9fa20 100644 --- a/src/basilisp/lang/compiler/nodes.py +++ b/src/basilisp/lang/compiler/nodes.py @@ -915,6 +915,7 @@ class VarRef(Node[Union[sym.Symbol, ISeq]], Assignable): env: NodeEnv return_var: bool = False is_assignable: bool = True + is_allow_var_indirection: bool = False children: Sequence[kw.Keyword] = vec.PersistentVector.empty() op: NodeOp = NodeOp.VAR top_level: bool = False diff --git a/tests/basilisp/compiler_test.py b/tests/basilisp/compiler_test.py index b73a1f9ec..441bd5c55 100644 --- a/tests/basilisp/compiler_test.py +++ b/tests/basilisp/compiler_test.py @@ -6501,6 +6501,19 @@ def test_warning_for_cross_ns_alias_reference( rf"could not resolve a direct link to Var 'm' \({ns}:\d+\)", ) + def test_no_warning_for_cross_ns_alias_reference_if_warning_enabled_but_suppressed_locally( + self, lcompile: CompileFn, other_ns, assert_no_matching_logs + ): + lcompile( + "(fn [] (^:no-warn-on-var-indirection other/m :z))", + opts={compiler.WARN_ON_VAR_INDIRECTION: True}, + ) + assert_no_matching_logs( + "basilisp.lang.compiler.generator", + logging.WARNING, + r"could not resolve a direct link to Var 'm' \(test:\d+\)", + ) + def test_no_warning_for_cross_ns_alias_reference_if_warning_disabled( self, lcompile: CompileFn, other_ns, assert_no_matching_logs ):