• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

basilisp-lang / basilisp / 10907555691

17 Sep 2024 04:54PM UTC coverage: 98.842% (-0.05%) from 98.893%
10907555691

Pull #1050

github

web-flow
Merge 7154d1bf0 into ce3eb81c5
Pull Request #1050: Emit warnings when requiring or importing with an already used alias

1885 of 1893 branches covered (99.58%)

Branch coverage included in aggregate %.

44 of 49 new or added lines in 5 files covered. (89.8%)

5 existing lines in 1 file now uncovered.

8615 of 8730 relevant lines covered (98.68%)

0.99 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.47
/src/basilisp/lang/compiler/analyzer.py
1
# pylint: disable=too-many-branches,too-many-lines,too-many-return-statements
2
import builtins
1✔
3
import collections
1✔
4
import contextlib
1✔
5
import functools
1✔
6
import inspect
1✔
7
import logging
1✔
8
import platform
1✔
9
import re
1✔
10
import sys
1✔
11
import uuid
1✔
12
from collections import defaultdict
1✔
13
from datetime import datetime
1✔
14
from decimal import Decimal
1✔
15
from fractions import Fraction
1✔
16
from functools import partial, wraps
1✔
17
from typing import (
1✔
18
    Any,
19
    Callable,
20
    Collection,
21
    Deque,
22
    FrozenSet,
23
    Iterable,
24
    List,
25
    Mapping,
26
    MutableMapping,
27
    MutableSet,
28
    Optional,
29
    Pattern,
30
    Set,
31
    Tuple,
32
    Type,
33
    TypeVar,
34
    Union,
35
    cast,
36
)
37

38
import attr
1✔
39

40
from basilisp.lang import keyword as kw
1✔
41
from basilisp.lang import list as llist
1✔
42
from basilisp.lang import map as lmap
1✔
43
from basilisp.lang import queue as lqueue
1✔
44
from basilisp.lang import reader as reader
1✔
45
from basilisp.lang import runtime as runtime
1✔
46
from basilisp.lang import set as lset
1✔
47
from basilisp.lang import symbol as sym
1✔
48
from basilisp.lang import vector as vec
1✔
49
from basilisp.lang.compiler.constants import (
1✔
50
    AMPERSAND,
51
    ARGLISTS_KW,
52
    COL_KW,
53
    DEFAULT_COMPILER_FILE_PATH,
54
    DOC_KW,
55
    END_COL_KW,
56
    END_LINE_KW,
57
    FILE_KW,
58
    LINE_KW,
59
    NAME_KW,
60
    NS_KW,
61
    REST_KW,
62
    SYM_ABSTRACT_MEMBERS_META_KEY,
63
    SYM_ABSTRACT_META_KEY,
64
    SYM_ASYNC_META_KEY,
65
    SYM_CLASSMETHOD_META_KEY,
66
    SYM_DEFAULT_META_KEY,
67
    SYM_DYNAMIC_META_KEY,
68
    SYM_INLINE_META_KW,
69
    SYM_KWARGS_META_KEY,
70
    SYM_MACRO_META_KEY,
71
    SYM_MUTABLE_META_KEY,
72
    SYM_NO_INLINE_META_KEY,
73
    SYM_NO_WARN_ON_REDEF_META_KEY,
74
    SYM_NO_WARN_ON_SHADOW_META_KEY,
75
    SYM_NO_WARN_ON_VAR_INDIRECTION_META_KEY,
76
    SYM_NO_WARN_WHEN_UNUSED_META_KEY,
77
    SYM_PRIVATE_META_KEY,
78
    SYM_PROPERTY_META_KEY,
79
    SYM_REDEF_META_KEY,
80
    SYM_STATICMETHOD_META_KEY,
81
    SYM_TAG_META_KEY,
82
    SYM_USE_VAR_INDIRECTION_KEY,
83
    VAR_IS_PROTOCOL_META_KEY,
84
    SpecialForm,
85
)
86
from basilisp.lang.compiler.exception import CompilerException, CompilerPhase
1✔
87
from basilisp.lang.compiler.nodes import (
1✔
88
    Assignable,
89
    Await,
90
    Binding,
91
    Catch,
92
    Const,
93
    ConstType,
94
    Def,
95
    DefType,
96
    DefTypeBase,
97
    DefTypeClassMethod,
98
    DefTypeMember,
99
    DefTypeMethod,
100
    DefTypeMethodArity,
101
    DefTypeProperty,
102
    DefTypePythonMember,
103
    DefTypeStaticMethod,
104
    Do,
105
    Fn,
106
    FnArity,
107
    FunctionContext,
108
    HostCall,
109
    HostField,
110
    If,
111
    Import,
112
    ImportAlias,
113
    Invoke,
114
    KeywordArgs,
115
    KeywordArgSupport,
116
    Let,
117
    LetFn,
118
    Local,
119
    LocalType,
120
    Loop,
121
)
122
from basilisp.lang.compiler.nodes import Map as MapNode
1✔
123
from basilisp.lang.compiler.nodes import (
1✔
124
    MaybeClass,
125
    MaybeHostForm,
126
    Node,
127
    NodeEnv,
128
    NodeOp,
129
    NodeSyntacticPosition,
130
    PyDict,
131
    PyList,
132
    PySet,
133
    PyTuple,
134
)
135
from basilisp.lang.compiler.nodes import Queue as QueueNode
1✔
136
from basilisp.lang.compiler.nodes import Quote, Recur, Reify, Require, RequireAlias
1✔
137
from basilisp.lang.compiler.nodes import Set as SetNode
1✔
138
from basilisp.lang.compiler.nodes import SetBang, SpecialFormNode, Throw, Try, VarRef
1✔
139
from basilisp.lang.compiler.nodes import Vector as VectorNode
1✔
140
from basilisp.lang.compiler.nodes import (
1✔
141
    WithMeta,
142
    Yield,
143
    deftype_or_reify_python_member_names,
144
)
145
from basilisp.lang.interfaces import IMeta, INamed, IRecord, ISeq, IType, IWithMeta
1✔
146
from basilisp.lang.runtime import Var
1✔
147
from basilisp.lang.typing import CompilerOpts, LispForm, ReaderForm
1✔
148
from basilisp.lang.util import OBJECT_DUNDER_METHODS, count, genname, is_abstract, munge
1✔
149
from basilisp.logconfig import TRACE
1✔
150
from basilisp.util import Maybe, partition
1✔
151

152
# Analyzer logging
153
logger = logging.getLogger(__name__)
1✔
154

155
# Analyzer options
156
GENERATE_AUTO_INLINES = kw.keyword("generate-auto-inlines")
1✔
157
INLINE_FUNCTIONS = kw.keyword("inline-functions")
1✔
158
WARN_ON_ARITY_MISMATCH = kw.keyword("warn-on-arity-mismatch")
1✔
159
WARN_ON_SHADOWED_NAME = kw.keyword("warn-on-shadowed-name")
1✔
160
WARN_ON_SHADOWED_VAR = kw.keyword("warn-on-shadowed-var")
1✔
161
WARN_ON_UNUSED_NAMES = kw.keyword("warn-on-unused-names")
1✔
162
WARN_ON_NON_DYNAMIC_SET = kw.keyword("warn-on-non-dynamic-set")
1✔
163

164
# Lisp AST node keywords
165
INIT = kw.keyword("init")
1✔
166
META = kw.keyword("meta")
1✔
167
FIXED_ARITY = kw.keyword("fixed-arity")
1✔
168
BODY = kw.keyword("body")
1✔
169
CATCHES = kw.keyword("catches")
1✔
170
FINALLY = kw.keyword("finally")
1✔
171

172
# Constants used in analyzing
173
AS = kw.keyword("as")
1✔
174
IMPLEMENTS = kw.keyword("implements")
1✔
175
INTERFACE = kw.keyword("interface")
1✔
176
STAR_STAR = sym.symbol("**")
1✔
177
_DOUBLE_DOT_MACRO_NAME = ".."
1✔
178
_BUILTINS_NS = "python"
1✔
179

180
# Symbols to be ignored for unused symbol warnings
181
_IGNORED_SYM = sym.symbol("_")
1✔
182
_MACRO_ENV_SYM = sym.symbol("&env")
1✔
183
_MACRO_FORM_SYM = sym.symbol("&form")
1✔
184
_NO_WARN_UNUSED_SYMS = lset.s(_IGNORED_SYM, _MACRO_ENV_SYM, _MACRO_FORM_SYM)
1✔
185

186

187
@attr.define
1✔
188
class RecurPoint:
1✔
189
    loop_id: str
1✔
190
    args: Collection[Binding] = ()
1✔
191

192

193
@attr.frozen
1✔
194
class SymbolTableEntry:
1✔
195
    binding: Binding
1✔
196
    used: bool = False
1✔
197
    warn_if_unused: bool = True
1✔
198

199
    @property
1✔
200
    def symbol(self) -> sym.Symbol:
1✔
201
        return self.binding.form
1✔
202

203
    @property
1✔
204
    def context(self) -> LocalType:
1✔
205
        return self.binding.local
1✔
206

207

208
@attr.define
1✔
209
class SymbolTable:
1✔
210
    name: str
1✔
211
    _is_context_boundary: bool = False
1✔
212
    _parent: Optional["SymbolTable"] = None
1✔
213
    _table: MutableMapping[sym.Symbol, SymbolTableEntry] = attr.ib(factory=dict)
1✔
214

215
    def new_symbol(
1✔
216
        self, s: sym.Symbol, binding: Binding, warn_if_unused: bool = True
217
    ) -> "SymbolTable":
218
        assert s == binding.form, "Binding symbol must match passed symbol"
1✔
219

220
        if s in self._table:
1✔
221
            self._table[s] = attr.evolve(
1✔
222
                self._table[s], binding=binding, warn_if_unused=warn_if_unused
223
            )
224
        else:
225
            self._table[s] = SymbolTableEntry(binding, warn_if_unused=warn_if_unused)
1✔
226
        return self
1✔
227

228
    def find_symbol(self, s: sym.Symbol) -> Optional[SymbolTableEntry]:
1✔
229
        if s in self._table:
1✔
230
            return self._table[s]
1✔
231
        if self._parent is None:
1✔
232
            return None
1✔
233
        return self._parent.find_symbol(s)
1✔
234

235
    def mark_used(self, s: sym.Symbol) -> None:
1✔
236
        """Mark the symbol s used in the current table or the first ancestor table
237
        which contains the symbol."""
238
        if s in self._table:
1✔
239
            old: SymbolTableEntry = self._table[s]
1✔
240
            if old.used:
1✔
241
                return
1✔
242
            self._table[s] = attr.evolve(old, used=True)
1✔
243
        elif self._parent is not None:
1✔
244
            self._parent.mark_used(s)
1✔
245
        else:  # pragma: no cover
246
            assert False, f"Symbol {s} not defined in any symbol table"
247

248
    def _warn_unused_names(self):
1✔
249
        """Log a warning message for locally bound names whose values are not used
250
        by the time the symbol table frame is being popped off the stack.
251

252
        The symbol table contains locally-bound symbols, recur point symbols, and
253
        symbols bound to var-args in generated Python functions. Only the locally-
254
        bound symbols are eligible for an unused warning, since it is not common
255
        that recur points will be used and user code is not permitted to directly
256
        access the var-args symbol (the compiler inserts an intermediate symbol
257
        which user code uses).
258

259
        Warnings will not be issued for symbols named '_', '&form', and '&env'. The
260
        latter symbols appear in macros and a great many macros will never use them."""
261
        assert logger.isEnabledFor(
1✔
262
            logging.WARNING
263
        ), "Only warn when logger is configured for WARNING level"
264
        ns = runtime.get_current_ns()
1✔
265
        for _, entry in self._table.items():
1✔
266
            if entry.symbol.name.startswith("_"):
1✔
267
                continue
1✔
268
            if entry.symbol in _NO_WARN_UNUSED_SYMS:
1✔
269
                continue
1✔
270
            if entry.warn_if_unused and not entry.used:
1✔
271
                code_loc = (
1✔
272
                    Maybe(entry.symbol.meta)
273
                    .map(lambda m: f": {m.val_at(reader.READER_LINE_KW)}")
274
                    .or_else_get("")
275
                )
276
                logger.warning(
1✔
277
                    f"symbol '{entry.symbol}' defined but not used ({ns}{code_loc})"
278
                )
279

280
    @contextlib.contextmanager
1✔
281
    def new_frame(
1✔
282
        self, name: str, is_context_boundary: bool, warn_on_unused_names: bool
283
    ):
284
        """Context manager for creating a new stack frame. If warn_on_unused_names is
285
        True and the logger is enabled for WARNING, call _warn_unused_names() on the
286
        child SymbolTable before it is popped."""
287
        new_frame = SymbolTable(
1✔
288
            name, is_context_boundary=is_context_boundary, parent=self
289
        )
290
        yield new_frame
1✔
291
        if warn_on_unused_names and logger.isEnabledFor(logging.WARNING):
1✔
292
            new_frame._warn_unused_names()
1✔
293

294
    def _as_env_map(self) -> MutableMapping[sym.Symbol, lmap.PersistentMap]:
1✔
295
        locals_ = {} if self._parent is None else self._parent._as_env_map()
1✔
296
        locals_.update({k: v.binding.to_map() for k, v in self._table.items()})
1✔
297
        return locals_
1✔
298

299
    def as_env_map(self) -> lmap.PersistentMap:
1✔
300
        """Return a map of symbols to the local binding objects in the
301
        local symbol table as of this call."""
302
        return lmap.map(self._as_env_map())
1✔
303

304
    @property
1✔
305
    def context_boundary(self) -> "SymbolTable":
1✔
306
        """Return the nearest context boundary parent symbol table to this one. If the
307
        current table is a context boundary, it will be returned directly.
308

309
        Context boundary symbol tables are symbol tables defined at the top level for
310
        major Python execution boundaries, such as modules (namespaces), functions
311
        (sync and async), and methods.
312

313
        Certain symbols (such as imports) are globally available in the execution
314
        context they are defined in once they have been created, context boundary
315
        symbol tables serve as the anchor points where we hoist these global symbols
316
        so they do not go out of scope when the local table frame is popped."""
317
        if self._is_context_boundary:
1✔
318
            return self
1✔
UNCOV
319
        assert (
×
320
            self._parent is not None
321
        ), "Top symbol table must always be a context boundary"
UNCOV
322
        return self._parent.context_boundary
×
323

324

325
class AnalyzerContext:
1✔
326
    __slots__ = (
1✔
327
        "_allow_unresolved_symbols",
328
        "_filename",
329
        "_func_ctx",
330
        "_is_quoted",
331
        "_opts",
332
        "_recur_points",
333
        "_should_macroexpand",
334
        "_st",
335
        "_syntax_pos",
336
    )
337

338
    def __init__(
1✔
339
        self,
340
        filename: Optional[str] = None,
341
        opts: Optional[CompilerOpts] = None,
342
        should_macroexpand: bool = True,
343
        allow_unresolved_symbols: bool = False,
344
    ) -> None:
345
        self._allow_unresolved_symbols = allow_unresolved_symbols
1✔
346
        self._filename = Maybe(filename).or_else_get(DEFAULT_COMPILER_FILE_PATH)
1✔
347
        self._func_ctx: Deque[FunctionContext] = collections.deque([])
1✔
348
        self._is_quoted: Deque[bool] = collections.deque([])
1✔
349
        self._opts = (
1✔
350
            Maybe(opts).map(lmap.map).or_else_get(lmap.PersistentMap.empty())  # type: ignore[arg-type, unused-ignore]
351
        )
352
        self._recur_points: Deque[RecurPoint] = collections.deque([])
1✔
353
        self._should_macroexpand = should_macroexpand
1✔
354
        self._st = collections.deque([SymbolTable("<Top>", is_context_boundary=True)])
1✔
355
        self._syntax_pos = collections.deque([NodeSyntacticPosition.EXPR])
1✔
356

357
    @property
1✔
358
    def current_ns(self) -> runtime.Namespace:
1✔
359
        return runtime.get_current_ns()
1✔
360

361
    @property
1✔
362
    def filename(self) -> str:
1✔
363
        return self._filename
1✔
364

365
    @property
1✔
366
    def should_generate_auto_inlines(self) -> bool:
1✔
367
        """If True, generate inline function defs for functions with boolean `^:inline`
368
        meta keys."""
369
        return self._opts.val_at(GENERATE_AUTO_INLINES, True)
1✔
370

371
    @property
1✔
372
    def should_inline_functions(self) -> bool:
1✔
373
        """If True, function calls may be inlined if an inline def is provided."""
374
        return self._opts.val_at(INLINE_FUNCTIONS, True)
1✔
375

376
    @property
1✔
377
    def warn_on_arity_mismatch(self) -> bool:
1✔
378
        """If True, warn when a Basilisp function invocation is detected with an
379
        unsupported number of arguments."""
380
        return self._opts.val_at(WARN_ON_ARITY_MISMATCH, True)
1✔
381

382
    @property
1✔
383
    def warn_on_unused_names(self) -> bool:
1✔
384
        """If True, warn when local names are unused."""
385
        return self._opts.val_at(WARN_ON_UNUSED_NAMES, True)
1✔
386

387
    @property
1✔
388
    def warn_on_shadowed_name(self) -> bool:
1✔
389
        """If True, warn when a name is shadowed in an inner scope.
390

391
        Implies warn_on_shadowed_var."""
392
        return self._opts.val_at(WARN_ON_SHADOWED_NAME, False)
1✔
393

394
    @property
1✔
395
    def warn_on_shadowed_var(self) -> bool:
1✔
396
        """If True, warn when a def'ed Var name is shadowed in an inner scope.
397

398
        Implied by warn_on_shadowed_name. The value of warn_on_shadowed_name
399
        supersedes the value of this flag."""
400
        return self.warn_on_shadowed_name or self._opts.val_at(
1✔
401
            WARN_ON_SHADOWED_VAR, False
402
        )
403

404
    @property
1✔
405
    def warn_on_non_dynamic_set(self) -> bool:
1✔
406
        """If True, warn when attempting to set! a Var not marked as ^:dynamic."""
407
        return self._opts.val_at(WARN_ON_NON_DYNAMIC_SET, True)
1✔
408

409
    @property
1✔
410
    def is_quoted(self) -> bool:
1✔
411
        try:
1✔
412
            return self._is_quoted[-1] is True
1✔
413
        except IndexError:
1✔
414
            return False
1✔
415

416
    @contextlib.contextmanager
1✔
417
    def quoted(self):
1✔
418
        self._is_quoted.append(True)
1✔
419
        yield
1✔
420
        self._is_quoted.pop()
1✔
421

422
    @property
1✔
423
    def should_allow_unresolved_symbols(self) -> bool:
1✔
424
        """If True, the analyzer will allow unresolved symbols. This is primarily
425
        useful for contexts by the `macroexpand` and `macroexpand-1` core
426
        functions. This would not be a good setting to use for normal compiler
427
        scenarios."""
428
        return self._allow_unresolved_symbols
1✔
429

430
    @property
1✔
431
    def should_macroexpand(self) -> bool:
1✔
432
        """Return True if macros should be expanded."""
433
        return self._should_macroexpand
1✔
434

435
    @property
1✔
436
    def func_ctx(self) -> Optional[FunctionContext]:
1✔
437
        """Return the current function or method context of the current node, if one.
438
        Return None otherwise.
439

440
        It is possible that the current function is defined inside other functions,
441
        so this does not imply anything about the nesting level of the current node."""
442
        try:
1✔
443
            return self._func_ctx[-1]
1✔
444
        except IndexError:
1✔
445
            return None
1✔
446

447
    @property
1✔
448
    def is_async_ctx(self) -> bool:
1✔
449
        """Return True if the current node appears inside of an async function
450
        definition. Return False otherwise.
451

452
        It is possible that the current function is defined inside other functions,
453
        so this does not imply anything about the nesting level of the current node."""
454
        return self.func_ctx == FunctionContext.ASYNC_FUNCTION
1✔
455

456
    @contextlib.contextmanager
1✔
457
    def new_func_ctx(self, context_type: FunctionContext):
1✔
458
        """Context manager which can be used to set a function or method context for
459
        child nodes to examine. A new function context is pushed onto the stack each
460
        time the Analyzer finds a new function or method definition, so there may be
461
        many nested function contexts."""
462
        self._func_ctx.append(context_type)
1✔
463
        yield
1✔
464
        self._func_ctx.pop()
1✔
465

466
    @property
1✔
467
    def recur_point(self) -> Optional[RecurPoint]:
1✔
468
        """Return the current recur point which applies to the current node, if there
469
        is one."""
470
        try:
1✔
471
            return self._recur_points[-1]
1✔
472
        except IndexError:
1✔
473
            return None
1✔
474

475
    @contextlib.contextmanager
1✔
476
    def new_recur_point(self, loop_id: str, args: Collection[Any] = ()):
1✔
477
        """Context manager which can be used to set a recur point for child nodes.
478
        A new recur point is pushed onto the stack each time the Analyzer finds a
479
        form which supports recursion (such as `fn*` or `loop*`), so there may be
480
        many recur points, though only one may be active at any given time for a
481
        node."""
482
        self._recur_points.append(RecurPoint(loop_id, args=args))
1✔
483
        yield
1✔
484
        self._recur_points.pop()
1✔
485

486
    @property
1✔
487
    def symbol_table(self) -> SymbolTable:
1✔
488
        return self._st[-1]
1✔
489

490
    def put_new_symbol(  # pylint: disable=too-many-arguments
1✔
491
        self,
492
        s: sym.Symbol,
493
        binding: Binding,
494
        warn_on_shadowed_name: bool = True,
495
        warn_on_shadowed_var: bool = True,
496
        warn_if_unused: bool = True,
497
        symbol_table: Optional[SymbolTable] = None,
498
    ):
499
        """Add a new symbol to the symbol table.
500

501
        This function allows individual warnings to be disabled for one run
502
        by supplying keyword arguments temporarily disabling those warnings.
503
        In certain cases, we do not want to issue warnings again for a
504
        previously checked case, so this is a simple way of disabling these
505
        warnings for those cases.
506

507
        There are cases where undesired warnings may be triggered non-locally,
508
        so the Python keyword arguments cannot be used to suppress unwanted
509
        warnings. For these cases, symbols may include the `:no-warn-on-shadow`
510
        metadata key to indicate that warnings for shadowing names from outer
511
        scopes should be suppressed. It is not currently possible to suppress
512
        Var shadowing warnings at the symbol level.
513

514
        If WARN_ON_SHADOWED_NAME compiler option is active and the
515
        warn_on_shadowed_name keyword argument is True, then a warning will be
516
        emitted if a local name is shadowed by another local name. Note that
517
        WARN_ON_SHADOWED_NAME implies WARN_ON_SHADOWED_VAR.
518

519
        If WARN_ON_SHADOWED_VAR compiler option is active and the
520
        warn_on_shadowed_var keyword argument is True, then a warning will be
521
        emitted if a named var is shadowed by a local name."""
522
        st = symbol_table or self.symbol_table
1✔
523
        no_warn_on_shadow = (
1✔
524
            Maybe(s.meta)
525
            .map(lambda m: m.val_at(SYM_NO_WARN_ON_SHADOW_META_KEY, False))
526
            .or_else_get(False)
527
        )
528
        if (
1✔
529
            not no_warn_on_shadow
530
            and warn_on_shadowed_name
531
            and self.warn_on_shadowed_name
532
        ):
533
            if st.find_symbol(s) is not None:
1✔
534
                logger.warning(f"name '{s}' shadows name from outer scope")
1✔
535
        if (
1✔
536
            warn_on_shadowed_name or warn_on_shadowed_var
537
        ) and self.warn_on_shadowed_var:
538
            if self.current_ns.find(s) is not None:
1✔
539
                logger.warning(f"name '{s}' shadows def'ed Var from outer scope")
1✔
540
        if s.meta is not None and s.meta.val_at(SYM_NO_WARN_WHEN_UNUSED_META_KEY, None):
1✔
541
            warn_if_unused = False
1✔
542
        st.new_symbol(s, binding, warn_if_unused=warn_if_unused)
1✔
543

544
    @contextlib.contextmanager
1✔
545
    def new_symbol_table(self, name: str, is_context_boundary: bool = False):
1✔
546
        old_st = self.symbol_table
1✔
547
        with old_st.new_frame(
1✔
548
            name,
549
            is_context_boundary,
550
            self.warn_on_unused_names,
551
        ) as st:
552
            self._st.append(st)
1✔
553
            yield st
1✔
554
            self._st.pop()
1✔
555

556
    @contextlib.contextmanager
1✔
557
    def hide_parent_symbol_table(self):
1✔
558
        """Hide the immediate parent symbol table by temporarily popping
559
        it off the stack.
560

561
        Obviously doing this could have serious adverse consequences if another
562
        new symbol table is added to the stack during this operation, so it
563
        should essentially NEVER be used.
564

565
        Right now, it is being used to hide fields and `this` symbol from
566
        static and class methods."""
567
        old_st = self._st.pop()
1✔
568
        try:
1✔
569
            yield self.symbol_table
1✔
570
        finally:
571
            self._st.append(old_st)
1✔
572

573
    @contextlib.contextmanager
1✔
574
    def expr_pos(self):
1✔
575
        """Context manager which indicates to immediate child nodes that they
576
        are in an expression syntactic position."""
577
        self._syntax_pos.append(NodeSyntacticPosition.EXPR)
1✔
578
        try:
1✔
579
            yield
1✔
580
        finally:
581
            self._syntax_pos.pop()
1✔
582

583
    @contextlib.contextmanager
1✔
584
    def stmt_pos(self):
1✔
585
        """Context manager which indicates to immediate child nodes that they
586
        are in a statement syntactic position."""
587
        self._syntax_pos.append(NodeSyntacticPosition.STMT)
1✔
588
        try:
1✔
589
            yield
1✔
590
        finally:
591
            self._syntax_pos.pop()
1✔
592

593
    @contextlib.contextmanager
1✔
594
    def parent_pos(self):
1✔
595
        """Context manager which indicates to immediate child nodes that they
596
        are in an equivalent syntactic position as their parent node.
597

598
        This context manager copies the top position and pushes a new value onto
599
        the stack, so the parent node's syntax position will not be lost."""
600
        self._syntax_pos.append(self.syntax_position)
1✔
601
        try:
1✔
602
            yield
1✔
603
        finally:
604
            self._syntax_pos.pop()
1✔
605

606
    @property
1✔
607
    def syntax_position(self) -> NodeSyntacticPosition:
1✔
608
        """Return the syntax position of the current node as indicated by its
609
        parent node."""
610
        return self._syntax_pos[-1]
1✔
611

612
    def get_node_env(self, pos: Optional[NodeSyntacticPosition] = None) -> NodeEnv:
1✔
613
        """Return the current Node environment.
614

615
        If a synax position is given, it will be included in the environment.
616
        Otherwise, the position will be set to None."""
617
        return NodeEnv(
1✔
618
            ns=self.current_ns, file=self.filename, pos=pos, func_ctx=self.func_ctx
619
        )
620

621
    def AnalyzerException(
1✔
622
        self,
623
        msg: str,
624
        form: Union[LispForm, None, ISeq] = None,
625
        lisp_ast: Optional[Node] = None,
626
    ) -> CompilerException:
627
        """Return a CompilerException annotated with the current filename and
628
        :analyzer compiler phase set. The remaining keyword arguments are passed
629
        directly to the constructor."""
630
        return CompilerException(
1✔
631
            msg,
632
            phase=CompilerPhase.ANALYZING,
633
            filename=self.filename,
634
            form=form,
635
            lisp_ast=lisp_ast,
636
        )
637

638

639
####################
640
# Private Utilities
641
####################
642

643

644
BoolMetaGetter = Callable[[Union[IMeta, Var]], bool]
1✔
645
MetaGetter = Callable[[Union[IMeta, Var]], Any]
1✔
646

647

648
def _bool_meta_getter(meta_kw: kw.Keyword) -> BoolMetaGetter:
1✔
649
    """Return a function which checks an object with metadata for a boolean
650
    value by meta_kw."""
651

652
    def has_meta_prop(o: Union[IMeta, Var]) -> bool:
1✔
653
        return bool(
1✔
654
            Maybe(o.meta).map(lambda m: m.val_at(meta_kw, None)).or_else_get(False)
655
        )
656

657
    return has_meta_prop
1✔
658

659

660
def _meta_getter(meta_kw: kw.Keyword) -> MetaGetter:
1✔
661
    """Return a function which checks an object with metadata for a value by
662
    meta_kw."""
663

664
    def get_meta_prop(o: Union[IMeta, Var]) -> Any:
1✔
665
        return Maybe(o.meta).map(lambda m: m.val_at(meta_kw, None)).value
1✔
666

667
    return get_meta_prop
1✔
668

669

670
_is_artificially_abstract = _bool_meta_getter(SYM_ABSTRACT_META_KEY)
1✔
671
_artificially_abstract_members = _meta_getter(SYM_ABSTRACT_MEMBERS_META_KEY)
1✔
672
_is_async = _bool_meta_getter(SYM_ASYNC_META_KEY)
1✔
673
_is_mutable = _bool_meta_getter(SYM_MUTABLE_META_KEY)
1✔
674
_is_py_classmethod = _bool_meta_getter(SYM_CLASSMETHOD_META_KEY)
1✔
675
_is_py_property = _bool_meta_getter(SYM_PROPERTY_META_KEY)
1✔
676
_is_py_staticmethod = _bool_meta_getter(SYM_STATICMETHOD_META_KEY)
1✔
677
_is_macro = _bool_meta_getter(SYM_MACRO_META_KEY)
1✔
678
_is_no_inline = _bool_meta_getter(SYM_NO_INLINE_META_KEY)
1✔
679
_is_allow_var_indirection = _bool_meta_getter(SYM_NO_WARN_ON_VAR_INDIRECTION_META_KEY)
1✔
680
_is_use_var_indirection = _bool_meta_getter(SYM_USE_VAR_INDIRECTION_KEY)
1✔
681
_inline_meta = _meta_getter(SYM_INLINE_META_KW)
1✔
682
_tag_meta = _meta_getter(SYM_TAG_META_KEY)
1✔
683

684

685
T_form = TypeVar("T_form", bound=ReaderForm)
1✔
686
T_node = TypeVar("T_node", bound=Node)
1✔
687
LispAnalyzer = Callable[[T_form, AnalyzerContext], T_node]
1✔
688

689

690
def _loc(form: T_form) -> Optional[Tuple[int, int, int, int]]:
1✔
691
    """Fetch the location of the form in the original filename from the
692
    input form, if it has metadata."""
693
    # Technically, IMeta is sufficient for fetching `form.meta` but the
694
    # reader only applies line and column metadata to IWithMeta instances
695
    if isinstance(form, IWithMeta):
1✔
696
        meta = form.meta
1✔
697
        if meta is not None:
1✔
698
            line = meta.get(reader.READER_LINE_KW)
1✔
699
            col = meta.get(reader.READER_COL_KW)
1✔
700
            end_line = meta.get(reader.READER_END_LINE_KW)
1✔
701
            end_col = meta.get(reader.READER_END_COL_KW)
1✔
702
            if (
1✔
703
                isinstance(line, int)
704
                and isinstance(col, int)
705
                and isinstance(end_line, int)
706
                and isinstance(end_col, int)
707
            ):
708
                return line, col, end_line, end_col
1✔
709
    return None
1✔
710

711

712
def _with_loc(f: LispAnalyzer[T_form, T_node]) -> LispAnalyzer[T_form, T_node]:
1✔
713
    """Attach any available location information from the input form to
714
    the node environment returned from the parsing function."""
715

716
    @wraps(f)
1✔
717
    def _analyze_form(form: T_form, ctx: AnalyzerContext) -> T_node:
1✔
718
        form_loc = _loc(form)
1✔
719
        if form_loc is None:
1✔
720
            return f(form, ctx)
1✔
721
        else:
722
            return cast(T_node, f(form, ctx).fix_missing_locations(form_loc))
1✔
723

724
    return _analyze_form
1✔
725

726

727
def _clean_meta(meta: Optional[lmap.PersistentMap]) -> Optional[lmap.PersistentMap]:
1✔
728
    """Remove reader metadata from the form's meta map."""
729
    if meta is None:
1✔
730
        return None
1✔
731
    else:
732
        new_meta = meta.dissoc(
1✔
733
            reader.READER_LINE_KW,
734
            reader.READER_COL_KW,
735
            reader.READER_END_LINE_KW,
736
            reader.READER_END_COL_KW,
737
        )
738
        return None if len(new_meta) == 0 else new_meta
1✔
739

740

741
def _body_ast(
1✔
742
    form: Union[llist.PersistentList, ISeq], ctx: AnalyzerContext
743
) -> Tuple[Iterable[Node], Node]:
744
    """Analyze the form and produce a body of statement nodes and a single
745
    return expression node.
746

747
    If the body is empty, return a constant node containing nil.
748

749
    If the parent indicates that it is in a statement syntactic position
750
    (and thus that it cannot return a value), the final node will be marked
751
    as a statement (rather than an expression) as well."""
752
    body_list = list(form)
1✔
753
    if body_list:
1✔
754
        *stmt_forms, ret_form = body_list
1✔
755

756
        with ctx.stmt_pos():
1✔
757
            body_stmts = list(map(lambda form: _analyze_form(form, ctx), stmt_forms))
1✔
758

759
        with ctx.parent_pos():
1✔
760
            body_expr = _analyze_form(ret_form, ctx)
1✔
761

762
        body = body_stmts + [body_expr]
1✔
763
    else:
764
        body = []
1✔
765

766
    if body:
1✔
767
        *stmts, ret = body
1✔
768
    else:
769
        stmts, ret = [], _const_node(None, ctx)
1✔
770
    return stmts, ret
1✔
771

772

773
def _call_args_ast(
1✔
774
    form: ISeq, ctx: AnalyzerContext
775
) -> Tuple[Iterable[Node], KeywordArgs]:
776
    """Return a tuple of positional arguments and keyword arguments, splitting at the
777
    keyword argument marker symbol '**'."""
778
    with ctx.expr_pos():
1✔
779
        nmarkers = sum(int(e == STAR_STAR) for e in form)
1✔
780
        if nmarkers > 1:
1✔
781
            raise ctx.AnalyzerException(
1✔
782
                "function and method invocations may have at most 1 keyword argument marker '**'",
783
                form=form,
784
            )
785
        elif nmarkers == 1:
1✔
786
            kwarg_marker = False
1✔
787
            pos, kws = [], []
1✔
788
            for arg in form:
1✔
789
                if arg == STAR_STAR:
1✔
790
                    kwarg_marker = True
1✔
791
                    continue
1✔
792
                if kwarg_marker:
1✔
793
                    kws.append(arg)
1✔
794
                else:
795
                    pos.append(arg)
1✔
796

797
            args = vec.vector(map(lambda form: _analyze_form(form, ctx), pos))
1✔
798
            kw_map = {}
1✔
799
            try:
1✔
800
                for k, v in partition(kws, 2):
1✔
801
                    if isinstance(k, kw.Keyword):
1✔
802
                        munged_k = munge(k.name, allow_builtins=True)
1✔
803
                    elif isinstance(k, str):
1✔
804
                        munged_k = munge(k, allow_builtins=True)
1✔
805
                    else:
806
                        raise ctx.AnalyzerException(
1✔
807
                            f"keys for keyword arguments must be keywords or strings, not '{type(k)}'",
808
                            form=k,
809
                        )
810

811
                    if munged_k in kw_map:
1✔
812
                        raise ctx.AnalyzerException(
1✔
813
                            "duplicate keyword argument key in function or method invocation",
814
                            form=k,
815
                        )
816

817
                    kw_map[munged_k] = _analyze_form(v, ctx)
1✔
818

819
            except ValueError as e:
1✔
820
                raise ctx.AnalyzerException(
1✔
821
                    "keyword arguments must appear in key/value pairs", form=form
822
                ) from e
823
            else:
824
                kwargs = lmap.map(kw_map)
1✔
825
        else:
826
            args = vec.vector(map(lambda form: _analyze_form(form, ctx), form))
1✔
827
            kwargs = lmap.PersistentMap.empty()
1✔
828

829
        return args, kwargs
1✔
830

831

832
def _tag_ast(form: Optional[LispForm], ctx: AnalyzerContext) -> Optional[Node]:
1✔
833
    if form is None:
1✔
834
        return None
1✔
835
    return _analyze_form(form, ctx)
1✔
836

837

838
def _with_meta(gen_node: LispAnalyzer[T_form, T_node]) -> LispAnalyzer[T_form, T_node]:
1✔
839
    """Wraps the node generated by gen_node in a :with-meta AST node if the
840
    original form has meta.
841

842
    :with-meta AST nodes are used for non-quoted collection literals and for
843
    function expressions."""
844

845
    @wraps(gen_node)
1✔
846
    def with_meta(form: T_form, ctx: AnalyzerContext) -> T_node:
1✔
847
        assert not ctx.is_quoted, "with-meta nodes are not used in quoted expressions"
1✔
848

849
        descriptor = gen_node(form, ctx)
1✔
850

851
        if isinstance(form, IMeta):
1✔
852
            assert isinstance(form.meta, (lmap.PersistentMap, type(None)))
1✔
853
            form_meta = _clean_meta(form.meta)
1✔
854
            if form_meta is not None:
1✔
855
                meta_ast = _analyze_form(form_meta, ctx)
1✔
856
                assert isinstance(meta_ast, MapNode) or (
1✔
857
                    isinstance(meta_ast, Const) and meta_ast.type == ConstType.MAP
858
                )
859
                return cast(
1✔
860
                    T_node,
861
                    WithMeta(
862
                        form=cast(LispForm, form),
863
                        meta=meta_ast,
864
                        expr=descriptor,
865
                        env=ctx.get_node_env(pos=ctx.syntax_position),
866
                    ),
867
                )
868

869
        return descriptor
1✔
870

871
    return with_meta
1✔
872

873

874
######################
875
# Analyzer Entrypoint
876
######################
877

878

879
@functools.singledispatch
1✔
880
def _analyze_form(form: Union[ReaderForm, ISeq], ctx: AnalyzerContext):
1✔
UNCOV
881
    raise ctx.AnalyzerException(f"Unexpected form type {type(form)}", form=form)  # type: ignore[arg-type]
×
882

883

884
################
885
# Special Forms
886
################
887

888

889
def _await_ast(form: ISeq, ctx: AnalyzerContext) -> Await:
1✔
890
    assert form.first == SpecialForm.AWAIT
1✔
891

892
    if not ctx.is_async_ctx:
1✔
893
        raise ctx.AnalyzerException(
1✔
894
            "await forms may not appear in non-async context", form=form
895
        )
896

897
    nelems = count(form)
1✔
898
    if nelems != 2:
1✔
899
        raise ctx.AnalyzerException(
1✔
900
            "await forms must contain 2 elements, as in: (await expr)", form=form
901
        )
902

903
    with ctx.expr_pos():
1✔
904
        expr = _analyze_form(runtime.nth(form, 1), ctx)
1✔
905

906
    return Await(
1✔
907
        form=form,
908
        expr=expr,
909
        env=ctx.get_node_env(pos=ctx.syntax_position),
910
    )
911

912

913
def __should_warn_on_redef(
1✔
914
    current_ns: runtime.Namespace,
915
    defsym: sym.Symbol,
916
    def_meta: Optional[lmap.PersistentMap],
917
) -> bool:
918
    """Return True if the compiler should emit a warning about this name being redefined."""
919
    if def_meta is not None and def_meta.val_at(SYM_NO_WARN_ON_REDEF_META_KEY, False):
1✔
920
        return False
1✔
921

922
    if defsym not in current_ns.interns:
1✔
923
        return False
1✔
924

925
    var = current_ns.find(defsym)
1✔
926
    assert var is not None, f"Var {defsym} cannot be none here"
1✔
927

928
    if var.meta is not None and var.meta.val_at(SYM_REDEF_META_KEY):
1✔
929
        return False
1✔
930
    else:
931
        return bool(var.is_bound)
1✔
932

933

934
def _def_ast(  # pylint: disable=too-many-locals,too-many-statements
1✔
935
    form: ISeq, ctx: AnalyzerContext
936
) -> Def:
937
    assert form.first == SpecialForm.DEF
1✔
938

939
    nelems = count(form)
1✔
940
    if nelems not in (2, 3, 4):
1✔
941
        raise ctx.AnalyzerException(
1✔
942
            "def forms must have between 2 and 4 elements, as in: (def name docstring? init?)",
943
            form=form,
944
        )
945

946
    name = runtime.nth(form, 1)
1✔
947
    if not isinstance(name, sym.Symbol):
1✔
948
        raise ctx.AnalyzerException(
1✔
949
            f"def names must be symbols, not {type(name)}", form=name
950
        )
951

952
    tag_ast = _tag_ast(_tag_meta(name), ctx)
1✔
953

954
    init_idx: Optional[int]
955
    children: vec.PersistentVector[kw.Keyword]
956
    if nelems == 2:
1✔
957
        init_idx = None
1✔
958
        doc = None
1✔
959
        children = vec.PersistentVector.empty()
1✔
960
    elif nelems == 3:
1✔
961
        init_idx = 2
1✔
962
        doc = None
1✔
963
        children = vec.v(INIT)
1✔
964
    else:
965
        init_idx = 3
1✔
966
        doc = runtime.nth(form, 2)
1✔
967
        if not isinstance(doc, str):
1✔
968
            raise ctx.AnalyzerException("def docstring must be a string", form=doc)
1✔
969
        children = vec.v(INIT)
1✔
970

971
    # Cache the current namespace
972
    current_ns = ctx.current_ns
1✔
973

974
    # Attach metadata relevant for the current process below.
975
    #
976
    # The reader line/col metadata will be attached to the form itself in the
977
    # happy path case (top-level bare def or top-level def returned from a
978
    # macro). Less commonly, we may have to rely on metadata attached to the
979
    # def symbol if, for example, the def form is returned by a macro wrapped
980
    # in a do or let form. In that case, the macroexpansion process won't have
981
    # any metadata to pass along to the expanded form, but the symbol itself
982
    # is likely to have metadata. In rare cases, we may not be able to get
983
    # any metadata. This may happen if the form and name were both generated
984
    # programmatically.
985
    def_loc = _loc(form) or _loc(name) or (None, None, None, None)
1✔
986
    if def_loc == (None, None, None, None):
1✔
987
        logger.warning(f"def line and column metadata not provided for Var {name}")
1✔
988
    if name.meta is None:
1✔
989
        logger.warning(f"def name symbol has no metadata for Var {name}")
1✔
990
        name = name.with_meta(lmap.EMPTY)
1✔
991
    def_node_env = ctx.get_node_env(pos=ctx.syntax_position)
1✔
992
    def_meta = _clean_meta(
1✔
993
        name.meta.update(  # type: ignore [union-attr]
994
            lmap.map(
995
                {
996
                    COL_KW: def_loc[1],
997
                    END_COL_KW: def_loc[3],
998
                    FILE_KW: def_node_env.file,
999
                    LINE_KW: def_loc[0],
1000
                    END_LINE_KW: def_loc[2],
1001
                    NAME_KW: name,
1002
                    NS_KW: current_ns,
1003
                }
1004
            )
1005
        )
1006
    )
1007
    assert def_meta is not None, "def metadata must be defined at this point"
1✔
1008
    if doc is not None:
1✔
1009
        def_meta = def_meta.assoc(DOC_KW, doc)
1✔
1010

1011
    # Var metadata is set both for the running Basilisp instance
1012
    # and cached as Python bytecode to be reread again. Argument lists
1013
    # are quoted so as not to resolve argument symbols. For the compiled
1014
    # and cached bytecode, this causes no trouble. However, for the case
1015
    # where we directly set the Var meta for the running Basilisp instance
1016
    # this causes problems since we'll end up getting something like
1017
    # `(quote ([] [v]))` rather than simply `([] [v])`.
1018
    arglists_meta = def_meta.val_at(ARGLISTS_KW)  # type: ignore
1✔
1019
    if isinstance(arglists_meta, llist.PersistentList):
1✔
1020
        assert arglists_meta.first == SpecialForm.QUOTE
1✔
1021
        var_meta = def_meta.update(  # type: ignore
1✔
1022
            {ARGLISTS_KW: runtime.nth(arglists_meta, 1)}
1023
        )
1024
    else:
1025
        var_meta = def_meta
1✔
1026

1027
    # Generation fails later if we use the same symbol we received, since
1028
    # its meta may contain values which fail to compile.
1029
    bare_name = sym.symbol(name.name)
1✔
1030

1031
    # Warn if this symbol is potentially being redefined (if the Var was
1032
    # previously bound)
1033
    if __should_warn_on_redef(current_ns, bare_name, def_meta):
1✔
1034
        logger.warning(
1✔
1035
            f"redefining Var '{bare_name}' in namespace {current_ns}:{def_loc[0]}"
1036
        )
1037

1038
    ns_sym = sym.symbol(current_ns.name)
1✔
1039
    var = Var.intern_unbound(
1✔
1040
        ns_sym,
1041
        bare_name,
1042
        dynamic=def_meta.val_at(SYM_DYNAMIC_META_KEY, False),  # type: ignore
1043
        meta=var_meta,
1044
    )
1045

1046
    # Set the value after interning the Var so the symbol is available for
1047
    # recursive definition.
1048
    if init_idx is not None:
1✔
1049
        with ctx.expr_pos():
1✔
1050
            init = _analyze_form(runtime.nth(form, init_idx), ctx)
1✔
1051

1052
        if isinstance(init, Fn):
1✔
1053
            # Attach the automatically generated inline function (if one exists) to the
1054
            # Var and def metadata. We do not need to do this for user-provided inline
1055
            # functions (for which `init.inline_fn` will be None) since those should
1056
            # already be attached to the meta.
1057
            if init.inline_fn is not None:
1✔
1058
                assert isinstance(var.meta.val_at(SYM_INLINE_META_KW), bool), (  # type: ignore[union-attr]
1✔
1059
                    "Cannot have a user-generated inline function and an automatically "
1060
                    "generated inline function"
1061
                )
1062
                var.meta.assoc(SYM_INLINE_META_KW, init.inline_fn)  # type: ignore[union-attr]
1✔
1063
                def_meta = def_meta.assoc(SYM_INLINE_META_KW, init.inline_fn.form)  # type: ignore[union-attr]
1✔
1064

1065
            if tag_ast is not None and any(
1✔
1066
                arity.tag is not None for arity in init.arities
1067
            ):
1068
                raise ctx.AnalyzerException(
1✔
1069
                    "def'ed Var :tag conflicts with defined function :tag",
1070
                    form=form,
1071
                )
1072
    else:
1073
        init = None
1✔
1074

1075
    descriptor = Def(
1✔
1076
        form=form,
1077
        name=bare_name,
1078
        var=var,
1079
        init=init,
1080
        doc=doc,
1081
        children=children,
1082
        env=def_node_env,
1083
        tag=tag_ast,
1084
    )
1085

1086
    # We still have to compile the meta here down to Python source code, so
1087
    # anything which is not constant below needs to be valid Basilisp code
1088
    # at the site it is called.
1089
    #
1090
    # We are roughly generating code like this:
1091
    #
1092
    # (def ^{:col  1
1093
    #        :file "<REPL Input>"
1094
    #        :line 1
1095
    #        :name 'some-name
1096
    #        :ns   ((.- basilisp.lang.runtime/Namespace get) 'basilisp.user)}
1097
    #       some-name
1098
    #       "some value")
1099
    meta_ast = _analyze_form(
1✔
1100
        def_meta.update(  # type: ignore
1101
            {
1102
                NAME_KW: llist.l(SpecialForm.QUOTE, bare_name),
1103
                NS_KW: llist.l(
1104
                    llist.l(
1105
                        SpecialForm.INTEROP_PROP,
1106
                        sym.symbol("Namespace", "basilisp.lang.runtime"),
1107
                        sym.symbol("get"),
1108
                    ),
1109
                    llist.l(SpecialForm.QUOTE, sym.symbol(current_ns.name)),
1110
                ),
1111
            }
1112
        ),
1113
        ctx,
1114
    )
1115

1116
    assert (isinstance(meta_ast, Const) and meta_ast.type == ConstType.MAP) or (
1✔
1117
        isinstance(meta_ast, MapNode)
1118
    )
1119
    existing_children = cast(vec.PersistentVector, descriptor.children)
1✔
1120
    return descriptor.assoc(
1✔
1121
        meta=meta_ast, children=vec.vector(runtime.cons(META, existing_children))
1122
    )
1123

1124

1125
def __deftype_method_param_bindings(
1✔
1126
    params: vec.PersistentVector, ctx: AnalyzerContext, special_form: sym.Symbol
1127
) -> Tuple[bool, int, List[Binding]]:
1128
    """Generate parameter bindings for `deftype*` or `reify*` methods.
1129

1130
    Return a tuple containing a boolean, indicating if the parameter bindings
1131
    contain a variadic binding, an integer indicating the fixed arity of the
1132
    parameter bindings, and the list of parameter bindings.
1133

1134
    Special cases for individual method types must be handled by their
1135
    respective handlers. This method will only produce vanilla ARG type
1136
    bindings."""
1137
    assert special_form in {SpecialForm.DEFTYPE, SpecialForm.REIFY}
1✔
1138

1139
    has_vargs, vargs_idx = False, 0
1✔
1140
    param_nodes = []
1✔
1141
    for i, s in enumerate(params):
1✔
1142
        if not isinstance(s, sym.Symbol):
1✔
1143
            raise ctx.AnalyzerException(
1✔
1144
                f"{special_form} method parameter name must be a symbol", form=s
1145
            )
1146

1147
        if s == AMPERSAND:
1✔
1148
            has_vargs = True
1✔
1149
            vargs_idx = i
1✔
1150
            break
1✔
1151

1152
        binding = Binding(
1✔
1153
            form=s,
1154
            name=s.name,
1155
            local=LocalType.ARG,
1156
            arg_id=i,
1157
            is_variadic=False,
1158
            env=ctx.get_node_env(),
1159
        )
1160
        param_nodes.append(binding)
1✔
1161
        ctx.put_new_symbol(s, binding)
1✔
1162

1163
    fixed_arity = len(param_nodes)
1✔
1164

1165
    if has_vargs:
1✔
1166
        try:
1✔
1167
            vargs_sym = params[vargs_idx + 1]
1✔
1168

1169
            if not isinstance(vargs_sym, sym.Symbol):
1✔
1170
                raise ctx.AnalyzerException(
1✔
1171
                    f"{special_form} method rest parameter name must be a symbol",
1172
                    form=vargs_sym,
1173
                )
1174

1175
            binding = Binding(
1✔
1176
                form=vargs_sym,
1177
                name=vargs_sym.name,
1178
                local=LocalType.ARG,
1179
                arg_id=vargs_idx + 1,
1180
                is_variadic=True,
1181
                env=ctx.get_node_env(),
1182
            )
1183
            param_nodes.append(binding)
1✔
1184
            ctx.put_new_symbol(vargs_sym, binding)
1✔
1185
        except IndexError:
1✔
1186
            raise ctx.AnalyzerException(
1✔
1187
                "Expected variadic argument name after '&'", form=params
1188
            ) from None
1189

1190
    return has_vargs, fixed_arity, param_nodes
1✔
1191

1192

1193
def __deftype_classmethod(
1✔
1194
    form: Union[llist.PersistentList, ISeq],
1195
    ctx: AnalyzerContext,
1196
    method_name: str,
1197
    args: vec.PersistentVector,
1198
    kwarg_support: Optional[KeywordArgSupport] = None,
1199
) -> DefTypeClassMethod:
1200
    """Emit a node for a :classmethod member of a `deftype*` form."""
1201
    with ctx.hide_parent_symbol_table(), ctx.new_symbol_table(
1✔
1202
        method_name, is_context_boundary=True
1203
    ):
1204
        try:
1✔
1205
            cls_arg = args[0]
1✔
1206
        except IndexError as e:
1✔
1207
            raise ctx.AnalyzerException(
1✔
1208
                "deftype* class method must include 'cls' argument", form=args
1209
            ) from e
1210
        else:
1211
            if not isinstance(cls_arg, sym.Symbol):
1✔
1212
                raise ctx.AnalyzerException(
1✔
1213
                    "deftype* class method 'cls' argument must be a symbol",
1214
                    form=args,
1215
                )
1216
            cls_binding = Binding(
1✔
1217
                form=cls_arg,
1218
                name=cls_arg.name,
1219
                local=LocalType.ARG,
1220
                env=ctx.get_node_env(),
1221
            )
1222
            ctx.put_new_symbol(cls_arg, cls_binding)
1✔
1223

1224
        params = args[1:]
1✔
1225
        has_vargs, fixed_arity, param_nodes = __deftype_method_param_bindings(
1✔
1226
            params, ctx, SpecialForm.DEFTYPE
1227
        )
1228
        with ctx.new_func_ctx(FunctionContext.CLASSMETHOD), ctx.expr_pos():
1✔
1229
            stmts, ret = _body_ast(runtime.nthrest(form, 2), ctx)
1✔
1230
        method = DefTypeClassMethod(
1✔
1231
            form=form,
1232
            name=method_name,
1233
            params=vec.vector(param_nodes),
1234
            fixed_arity=fixed_arity,
1235
            is_variadic=has_vargs,
1236
            kwarg_support=kwarg_support,
1237
            body=Do(
1238
                form=form.rest,
1239
                statements=vec.vector(stmts),
1240
                ret=ret,
1241
                is_body=True,
1242
                # Use the argument vector or first body statement, whichever
1243
                # exists, for metadata.
1244
                env=ctx.get_node_env(),
1245
            ),
1246
            class_local=cls_binding,
1247
            env=ctx.get_node_env(),
1248
        )
1249
        method.visit(partial(_assert_no_recur, ctx))
1✔
1250
        return method
1✔
1251

1252

1253
def __deftype_or_reify_method(  # pylint: disable=too-many-arguments,too-many-locals
1✔
1254
    form: Union[llist.PersistentList, ISeq],
1255
    ctx: AnalyzerContext,
1256
    method_name: str,
1257
    args: vec.PersistentVector,
1258
    special_form: sym.Symbol,
1259
    kwarg_support: Optional[KeywordArgSupport] = None,
1260
) -> DefTypeMethodArity:
1261
    """Emit a node for a method member of a `deftype*` or `reify*` form."""
1262
    assert special_form in {SpecialForm.DEFTYPE, SpecialForm.REIFY}
1✔
1263

1264
    with ctx.new_symbol_table(method_name, is_context_boundary=True):
1✔
1265
        try:
1✔
1266
            this_arg = args[0]
1✔
1267
        except IndexError as e:
1✔
1268
            raise ctx.AnalyzerException(
1✔
1269
                f"{special_form} method must include 'this' or 'self' argument",
1270
                form=args,
1271
            ) from e
1272
        else:
1273
            if not isinstance(this_arg, sym.Symbol):
1✔
1274
                raise ctx.AnalyzerException(
1✔
1275
                    f"{special_form} method 'this' argument must be a symbol", form=args
1276
                )
1277
            this_binding = Binding(
1✔
1278
                form=this_arg,
1279
                name=this_arg.name,
1280
                local=LocalType.THIS,
1281
                env=ctx.get_node_env(),
1282
            )
1283
            ctx.put_new_symbol(this_arg, this_binding, warn_if_unused=False)
1✔
1284

1285
        params = args[1:]
1✔
1286
        has_vargs, fixed_arity, param_nodes = __deftype_method_param_bindings(
1✔
1287
            params, ctx, special_form
1288
        )
1289

1290
        loop_id = genname(method_name)
1✔
1291
        with ctx.new_recur_point(loop_id, param_nodes):
1✔
1292
            with ctx.new_func_ctx(FunctionContext.METHOD), ctx.expr_pos():
1✔
1293
                stmts, ret = _body_ast(runtime.nthrest(form, 2), ctx)
1✔
1294
            method = DefTypeMethodArity(
1✔
1295
                form=form,
1296
                name=method_name,
1297
                this_local=this_binding,
1298
                params=vec.vector(param_nodes),
1299
                fixed_arity=fixed_arity,
1300
                is_variadic=has_vargs,
1301
                kwarg_support=kwarg_support,
1302
                body=Do(
1303
                    form=form.rest,
1304
                    statements=vec.vector(stmts),
1305
                    ret=ret,
1306
                    is_body=True,
1307
                    # Use the argument vector or first body statement, whichever
1308
                    # exists, for metadata.
1309
                    env=ctx.get_node_env(),
1310
                ),
1311
                loop_id=loop_id,
1312
                env=ctx.get_node_env(),
1313
            )
1314
            method.visit(partial(_assert_recur_is_tail, ctx))
1✔
1315
            return method
1✔
1316

1317

1318
def __deftype_or_reify_property(
1✔
1319
    form: Union[llist.PersistentList, ISeq],
1320
    ctx: AnalyzerContext,
1321
    method_name: str,
1322
    args: vec.PersistentVector,
1323
    special_form: sym.Symbol,
1324
) -> DefTypeProperty:
1325
    """Emit a node for a :property member of a `deftype*` or `reify*` form."""
1326
    assert special_form in {SpecialForm.DEFTYPE, SpecialForm.REIFY}
1✔
1327

1328
    with ctx.new_symbol_table(method_name, is_context_boundary=True):
1✔
1329
        try:
1✔
1330
            this_arg = args[0]
1✔
1331
        except IndexError as e:
1✔
1332
            raise ctx.AnalyzerException(
1✔
1333
                f"{special_form} property must include 'this' or 'self' argument",
1334
                form=args,
1335
            ) from e
1336
        else:
1337
            if not isinstance(this_arg, sym.Symbol):
1✔
1338
                raise ctx.AnalyzerException(
1✔
1339
                    f"{special_form} property 'this' argument must be a symbol",
1340
                    form=args,
1341
                )
1342
            this_binding = Binding(
1✔
1343
                form=this_arg,
1344
                name=this_arg.name,
1345
                local=LocalType.THIS,
1346
                env=ctx.get_node_env(),
1347
            )
1348
            ctx.put_new_symbol(this_arg, this_binding, warn_if_unused=False)
1✔
1349

1350
        params = args[1:]
1✔
1351
        has_vargs, _, param_nodes = __deftype_method_param_bindings(
1✔
1352
            params, ctx, special_form
1353
        )
1354

1355
        if len(param_nodes) > 0:
1✔
1356
            raise ctx.AnalyzerException(
1✔
1357
                f"{special_form} properties may not specify arguments", form=form
1358
            )
1359

1360
        assert not has_vargs, f"{special_form} properties may not have arguments"
1✔
1361

1362
        with ctx.new_func_ctx(FunctionContext.PROPERTY), ctx.expr_pos():
1✔
1363
            stmts, ret = _body_ast(runtime.nthrest(form, 2), ctx)
1✔
1364
        prop = DefTypeProperty(
1✔
1365
            form=form,
1366
            name=method_name,
1367
            this_local=this_binding,
1368
            params=vec.vector(param_nodes),
1369
            body=Do(
1370
                form=form.rest,
1371
                statements=vec.vector(stmts),
1372
                ret=ret,
1373
                is_body=True,
1374
                # Use the argument vector or first body statement, whichever
1375
                # exists, for metadata.
1376
                env=ctx.get_node_env(),
1377
            ),
1378
            env=ctx.get_node_env(),
1379
        )
1380
        prop.visit(partial(_assert_no_recur, ctx))
1✔
1381
        return prop
1✔
1382

1383

1384
def __deftype_staticmethod(
1✔
1385
    form: Union[llist.PersistentList, ISeq],
1386
    ctx: AnalyzerContext,
1387
    method_name: str,
1388
    args: vec.PersistentVector,
1389
    kwarg_support: Optional[KeywordArgSupport] = None,
1390
) -> DefTypeStaticMethod:
1391
    """Emit a node for a :staticmethod member of a `deftype*` form."""
1392
    with ctx.hide_parent_symbol_table(), ctx.new_symbol_table(
1✔
1393
        method_name, is_context_boundary=True
1394
    ):
1395
        has_vargs, fixed_arity, param_nodes = __deftype_method_param_bindings(
1✔
1396
            args, ctx, SpecialForm.DEFTYPE
1397
        )
1398
        with ctx.new_func_ctx(FunctionContext.STATICMETHOD), ctx.expr_pos():
1✔
1399
            stmts, ret = _body_ast(runtime.nthrest(form, 2), ctx)
1✔
1400
        method = DefTypeStaticMethod(
1✔
1401
            form=form,
1402
            name=method_name,
1403
            params=vec.vector(param_nodes),
1404
            fixed_arity=fixed_arity,
1405
            is_variadic=has_vargs,
1406
            kwarg_support=kwarg_support,
1407
            body=Do(
1408
                form=form.rest,
1409
                statements=vec.vector(stmts),
1410
                ret=ret,
1411
                is_body=True,
1412
                # Use the argument vector or first body statement, whichever
1413
                # exists, for metadata.
1414
                env=ctx.get_node_env(),
1415
            ),
1416
            env=ctx.get_node_env(),
1417
        )
1418
        method.visit(partial(_assert_no_recur, ctx))
1✔
1419
        return method
1✔
1420

1421

1422
def __deftype_or_reify_prop_or_method_arity(
1✔
1423
    form: Union[llist.PersistentList, ISeq],
1424
    ctx: AnalyzerContext,
1425
    special_form: sym.Symbol,
1426
) -> Union[DefTypeMethodArity, DefTypePythonMember]:
1427
    """Emit either a `deftype*` or `reify*` property node or an arity of a `deftype*`
1428
    or `reify*` method.
1429

1430
    Unlike standard `fn*` definitions, multiple arities for a single method are
1431
    not defined within some containing node. As such, we can only emit either a
1432
    full property node (since properties may not be multi-arity) or the single
1433
    arity of a method, classmethod, or staticmethod.
1434

1435
    The type of the member node is determined by the presence or absence of certain
1436
    metadata elements on the input form (or the form's first member, typically a
1437
    symbol naming that member)."""
1438
    assert special_form in {SpecialForm.DEFTYPE, SpecialForm.REIFY}
1✔
1439

1440
    if not isinstance(form.first, sym.Symbol):
1✔
1441
        raise ctx.AnalyzerException(
1✔
1442
            f"{special_form} method must be named by symbol: (name [& args] & body)",
1443
            form=form.first,
1444
        )
1445
    method_name = form.first.name
1✔
1446

1447
    is_classmethod = _is_py_classmethod(form.first) or (
1✔
1448
        isinstance(form, IMeta) and _is_py_classmethod(form)
1449
    )
1450
    is_property = _is_py_property(form.first) or (
1✔
1451
        isinstance(form, IMeta) and _is_py_property(form)
1452
    )
1453
    is_staticmethod = _is_py_staticmethod(form.first) or (
1✔
1454
        isinstance(form, IMeta) and _is_py_staticmethod(form)
1455
    )
1456

1457
    if special_form == SpecialForm.REIFY and (is_classmethod or is_staticmethod):
1✔
1458
        raise ctx.AnalyzerException(
1✔
1459
            f"{special_form} does not support classmethod or staticmethod members",
1460
            form=form,
1461
        )
1462

1463
    if sum([is_classmethod, is_property, is_staticmethod]) not in {0, 1}:
1✔
1464
        raise ctx.AnalyzerException(
1✔
1465
            f"{special_form} member may be only one of: :classmethod, :property, "
1466
            "or :staticmethod",
1467
            form=form,
1468
        )
1469

1470
    args = runtime.nth(form, 1)
1✔
1471
    if not isinstance(args, vec.PersistentVector):
1✔
1472
        raise ctx.AnalyzerException(
1✔
1473
            f"{special_form} member arguments must be vector, not {type(args)}",
1474
            form=args,
1475
        )
1476

1477
    kwarg_meta = __fn_kwargs_support(ctx, form.first) or (
1✔
1478
        isinstance(form, IMeta) and __fn_kwargs_support(ctx, form)
1479
    )
1480
    kwarg_support = None if isinstance(kwarg_meta, bool) else kwarg_meta
1✔
1481

1482
    if is_classmethod:
1✔
1483
        return __deftype_classmethod(
1✔
1484
            form, ctx, method_name, args, kwarg_support=kwarg_support
1485
        )
1486
    elif is_property:
1✔
1487
        if kwarg_support is not None:
1✔
1488
            raise ctx.AnalyzerException(
1✔
1489
                f"{special_form} properties may not declare keyword argument support",
1490
                form=form,
1491
            )
1492

1493
        return __deftype_or_reify_property(form, ctx, method_name, args, special_form)
1✔
1494
    elif is_staticmethod:
1✔
1495
        return __deftype_staticmethod(
1✔
1496
            form, ctx, method_name, args, kwarg_support=kwarg_support
1497
        )
1498
    else:
1499
        return __deftype_or_reify_method(
1✔
1500
            form, ctx, method_name, args, special_form, kwarg_support=kwarg_support
1501
        )
1502

1503

1504
def __deftype_or_reify_method_node_from_arities(
1✔
1505
    form: Union[llist.PersistentList, ISeq],
1506
    ctx: AnalyzerContext,
1507
    arities: List[DefTypeMethodArity],
1508
    special_form: sym.Symbol,
1509
) -> DefTypeMember:
1510
    """Roll all of the collected `deftype*` or `reify*` arities up into a single
1511
    method node."""
1512
    assert special_form in {SpecialForm.DEFTYPE, SpecialForm.REIFY}
1✔
1513

1514
    fixed_arities: MutableSet[int] = set()
1✔
1515
    fixed_arity_for_variadic: Optional[int] = None
1✔
1516
    num_variadic = 0
1✔
1517
    for arity in arities:
1✔
1518
        if arity.is_variadic:
1✔
1519
            if num_variadic > 0:
1✔
1520
                raise ctx.AnalyzerException(
1✔
1521
                    f"{special_form} method may have at most 1 variadic arity",
1522
                    form=arity.form,
1523
                )
1524
            fixed_arity_for_variadic = arity.fixed_arity
1✔
1525
            num_variadic += 1
1✔
1526
        else:
1527
            if arity.fixed_arity in fixed_arities:
1✔
1528
                raise ctx.AnalyzerException(
1✔
1529
                    f"{special_form} may not have multiple methods with the same "
1530
                    "fixed arity",
1531
                    form=arity.form,
1532
                )
1533
            fixed_arities.add(arity.fixed_arity)
1✔
1534

1535
    if fixed_arity_for_variadic is not None and any(
1✔
1536
        fixed_arity_for_variadic < arity for arity in fixed_arities
1537
    ):
1538
        raise ctx.AnalyzerException(
1✔
1539
            "variadic arity may not have fewer fixed arity arguments than any other arities",
1540
            form=form,
1541
        )
1542

1543
    assert (
1✔
1544
        len(set(arity.name for arity in arities)) <= 1
1545
    ), "arities must have the same name defined"
1546

1547
    if len(arities) > 1 and any(arity.kwarg_support is not None for arity in arities):
1✔
1548
        raise ctx.AnalyzerException(
1✔
1549
            f"multi-arity {special_form} methods may not declare support for "
1550
            "keyword arguments",
1551
            form=form,
1552
        )
1553

1554
    return DefTypeMethod(
1✔
1555
        form=form,
1556
        name=arities[0].name,
1557
        max_fixed_arity=max(arity.fixed_arity for arity in arities),
1558
        arities=vec.vector(arities),
1559
        is_variadic=num_variadic == 1,
1560
        env=ctx.get_node_env(),
1561
    )
1562

1563

1564
def __deftype_or_reify_impls(  # pylint: disable=too-many-branches,too-many-locals  # noqa: MC0001
1✔
1565
    form: ISeq, ctx: AnalyzerContext, special_form: sym.Symbol
1566
) -> Tuple[List[DefTypeBase], List[DefTypeMember]]:
1567
    """Roll up `deftype*` and `reify*` declared bases and method implementations."""
1568
    assert special_form in {SpecialForm.DEFTYPE, SpecialForm.REIFY}
1✔
1569

1570
    if runtime.to_seq(form) is None:
1✔
1571
        return [], []
1✔
1572

1573
    if not isinstance(form.first, kw.Keyword) or form.first != IMPLEMENTS:
1✔
1574
        raise ctx.AnalyzerException(
1✔
1575
            f"{special_form} forms must declare which interfaces they implement",
1576
            form=form,
1577
        )
1578

1579
    implements = runtime.nth(form, 1)
1✔
1580
    if not isinstance(implements, vec.PersistentVector):
1✔
1581
        raise ctx.AnalyzerException(
1✔
1582
            f"{special_form} interfaces must be declared as "
1583
            ":implements [Interface1 Interface2 ...]",
1584
            form=implements,
1585
        )
1586

1587
    interface_names: MutableSet[sym.Symbol] = set()
1✔
1588
    interfaces = []
1✔
1589
    for iface in implements:
1✔
1590
        if not isinstance(iface, sym.Symbol):
1✔
1591
            raise ctx.AnalyzerException(
1✔
1592
                f"{special_form} interfaces must be symbols", form=iface
1593
            )
1594

1595
        if iface in interface_names:
1✔
1596
            raise ctx.AnalyzerException(
1✔
1597
                f"{special_form} interfaces may only appear once in :implements vector",
1598
                form=iface,
1599
            )
1600
        interface_names.add(iface)
1✔
1601

1602
        current_interface = _analyze_form(iface, ctx)
1✔
1603
        if not isinstance(current_interface, (MaybeClass, MaybeHostForm, VarRef)):
1✔
1604
            raise ctx.AnalyzerException(
1✔
1605
                f"{special_form} interface implementation must be an existing interface",
1606
                form=iface,
1607
            )
1608
        interfaces.append(current_interface)
1✔
1609

1610
    # Use the insertion-order preserving capabilities of a dictionary with 'True'
1611
    # keys to act as an ordered set of members we've seen. We don't want to register
1612
    # duplicates.
1613
    member_order = {}
1✔
1614
    methods: MutableMapping[str, List[DefTypeMethodArity]] = collections.defaultdict(
1✔
1615
        list
1616
    )
1617
    py_members: MutableMapping[str, DefTypePythonMember] = {}
1✔
1618
    for elem in runtime.nthrest(form, 2):
1✔
1619
        if not isinstance(elem, ISeq):
1✔
1620
            raise ctx.AnalyzerException(
1✔
1621
                f"{special_form} must consist of interface or protocol names and methods",
1622
                form=elem,
1623
            )
1624

1625
        member = __deftype_or_reify_prop_or_method_arity(elem, ctx, special_form)
1✔
1626
        member_order[member.name] = True
1✔
1627
        if isinstance(
1✔
1628
            member, (DefTypeClassMethod, DefTypeProperty, DefTypeStaticMethod)
1629
        ):
1630
            if member.name in py_members:
1✔
1631
                raise ctx.AnalyzerException(
1✔
1632
                    f"{special_form} class methods, properties, and static methods "
1633
                    "may only have one arity defined",
1634
                    form=elem,
1635
                    lisp_ast=member,
1636
                )
1637
            elif member.name in methods:
1✔
1638
                raise ctx.AnalyzerException(
1✔
1639
                    f"{special_form} class method, property, or static method name "
1640
                    "already defined as a method",
1641
                    form=elem,
1642
                    lisp_ast=member,
1643
                )
1644
            py_members[member.name] = member
1✔
1645
        else:
1646
            if member.name in py_members:
1✔
1647
                raise ctx.AnalyzerException(
1✔
1648
                    f"{special_form} method name already defined as a class method, "
1649
                    "property, or static method",
1650
                    form=elem,
1651
                    lisp_ast=member,
1652
                )
1653
            methods[member.name].append(member)
1✔
1654

1655
    members: List[DefTypeMember] = []
1✔
1656
    for member_name in member_order:
1✔
1657
        arities = methods.get(member_name)
1✔
1658
        if arities is not None:
1✔
1659
            members.append(
1✔
1660
                __deftype_or_reify_method_node_from_arities(
1661
                    form, ctx, arities, special_form
1662
                )
1663
            )
1664
            continue
1✔
1665

1666
        py_member = py_members.get(member_name)
1✔
1667
        assert py_member is not None, "Member must be a method or property"
1✔
1668
        members.append(py_member)
1✔
1669

1670
    return interfaces, members
1✔
1671

1672

1673
_var_is_protocol = _bool_meta_getter(VAR_IS_PROTOCOL_META_KEY)
1✔
1674

1675

1676
def __is_deftype_member(mem) -> bool:
1✔
1677
    """Return True if `mem` names a valid `deftype*` member."""
1678
    return inspect.isroutine(mem) or isinstance(mem, (property, staticmethod))
1✔
1679

1680

1681
def __is_reify_member(mem) -> bool:
1✔
1682
    """Return True if `mem` names a valid `reify*` member."""
1683
    return inspect.isroutine(mem) or isinstance(mem, property)
1✔
1684

1685

1686
if platform.python_implementation() == "CPython":
1✔
1687

1688
    def __is_type_weakref(tp: Type) -> bool:
1✔
1689
        return getattr(tp, "__weakrefoffset__", 0) > 0
1✔
1690

1691
else:
1692

1693
    def __is_type_weakref(tp: Type) -> bool:  # pylint: disable=unused-argument
1✔
1694
        return True
1✔
1695

1696

1697
def __get_artificially_abstract_members(
1✔
1698
    ctx: AnalyzerContext, special_form: sym.Symbol, interface: DefTypeBase
1699
) -> Set[str]:
1700
    if (
1✔
1701
        declared_abstract_members := _artificially_abstract_members(
1702
            cast(IMeta, interface.form)
1703
        )
1704
    ) is None:
1705
        return set()
1✔
1706

1707
    if (
1✔
1708
        not isinstance(declared_abstract_members, lset.PersistentSet)
1709
        or len(declared_abstract_members) == 0
1710
    ):
1711
        raise ctx.AnalyzerException(
1✔
1712
            f"{special_form} artificially abstract members must be a set of keywords",
1713
            form=interface.form,
1714
        )
1715

1716
    members = set()
1✔
1717
    for mem in declared_abstract_members:
1✔
1718
        if isinstance(mem, INamed):
1✔
1719
            if mem.ns is not None:
1✔
1720
                logger.warning(
1✔
1721
                    "Unexpected namespace for artificially abstract member to "
1722
                    f"{special_form}: {mem}"
1723
                )
1724
            members.add(mem.name)
1✔
1725
        elif isinstance(mem, str):
1✔
1726
            members.add(mem)
1✔
1727
        else:
1728
            raise ctx.AnalyzerException(
1✔
1729
                f"{special_form} artificially abstract member names must be one of: "
1730
                f"str, keyword, or symbol; got {type(mem)}",
1731
                form=interface.form,
1732
            )
1733
    return members
1✔
1734

1735

1736
@attr.define
1✔
1737
class _TypeAbstractness:
1✔
1738
    """
1739
    :ivar is_statically_verified_as_abstract: a boolean value which, if True,
1740
        indicates that all bases have been statically verified abstract; if False,
1741
        indicates at least one base could not be statically verified
1742
    :ivar artificially_abstract_supertypes: the set of all super-types which have
1743
        been marked as artificially abstract
1744
    :ivar supertype_already_weakref: if True, a supertype is already marked as
1745
        weakref and therefore the resulting type cannot add "__weakref__" to the
1746
        slots list to enable weakref support
1747
    """
1748

1749
    is_statically_verified_as_abstract: bool
1✔
1750
    artificially_abstract_supertypes: lset.PersistentSet[DefTypeBase]
1✔
1751
    supertype_already_weakref: bool
1✔
1752

1753

1754
def __deftype_and_reify_impls_are_all_abstract(  # pylint: disable=too-many-locals,too-many-statements
1✔
1755
    ctx: AnalyzerContext,
1756
    special_form: sym.Symbol,
1757
    fields: Iterable[str],
1758
    interfaces: Iterable[DefTypeBase],
1759
    members: Iterable[DefTypeMember],
1760
) -> _TypeAbstractness:
1761
    """Return an object indicating the abstractness of the `deftype*` or `reify*`
1762
    super-types.
1763

1764
    In certain cases, such as in macro definitions and potentially inside of
1765
    functions, the compiler will be unable to resolve the named super-type as an
1766
    object during compilation and these checks will need to be deferred to runtime.
1767
    In these cases, the compiler will wrap the emitted class in a decorator that
1768
    performs the checks when the class is compiled by the Python compiler.
1769

1770
    The Python ecosystem is much less strict with its use of `abc.ABC` to define
1771
    interfaces than Java (which has a native `interface` construct), so even in cases
1772
    where a type may be _in practice_ an interface or ABC, the compiler would not
1773
    permit you to declare such types as supertypes because they do not themselves
1774
    inherit from `abc.ABC`. In these cases, users can mark the type as artificially
1775
    abstract with the `:abstract` metadata key.
1776

1777
    For normal compile-time errors, an `AnalyzerException` will be raised."""
1778
    assert special_form in {SpecialForm.DEFTYPE, SpecialForm.REIFY}
1✔
1779

1780
    supertype_possibly_weakref = []
1✔
1781
    unverifiably_abstract = set()
1✔
1782
    artificially_abstract: Set[DefTypeBase] = set()
1✔
1783
    artificially_abstract_base_members: Set[str] = set()
1✔
1784
    is_member = {
1✔
1785
        SpecialForm.DEFTYPE: __is_deftype_member,
1786
        SpecialForm.REIFY: __is_reify_member,
1787
    }[special_form]
1788

1789
    field_names = frozenset(fields)
1✔
1790
    member_names = frozenset(deftype_or_reify_python_member_names(members))
1✔
1791
    all_member_names = field_names.union(member_names)
1✔
1792
    all_interface_methods: Set[str] = set()
1✔
1793
    for interface in interfaces:
1✔
1794
        if isinstance(interface, (MaybeClass, MaybeHostForm)):
1✔
1795
            interface_type = interface.target
1✔
1796
        else:
1797
            assert isinstance(
1✔
1798
                interface, VarRef
1799
            ), "Interface must be MaybeClass, MaybeHostForm, or VarRef"
1800
            if not interface.var.is_bound:
1✔
1801
                logger.log(
1✔
1802
                    TRACE,
1803
                    f"{special_form} interface Var '{interface.form}' is not bound "
1804
                    "and cannot be checked for abstractness; deferring to runtime",
1805
                )
1806
                unverifiably_abstract.add(interface)
1✔
1807
                if isinstance(interface.form, IMeta) and _is_artificially_abstract(
1✔
1808
                    interface.form
1809
                ):
1810
                    artificially_abstract.add(interface)
1✔
1811
                continue
1✔
1812

1813
            # Protocols are defined as maps, with the interface being simply a member
1814
            # of the map, denoted by the keyword `:interface`.
1815
            if _var_is_protocol(interface.var):
1✔
1816
                proto_map = interface.var.value
1✔
1817
                assert isinstance(proto_map, lmap.PersistentMap)
1✔
1818
                interface_type = proto_map.val_at(INTERFACE)
1✔
1819
            else:
1820
                interface_type = interface.var.value
1✔
1821

1822
        if interface_type is object:
1✔
1823
            continue
1✔
1824

1825
        if isinstance(interface.form, IMeta) and _is_artificially_abstract(
1✔
1826
            interface.form
1827
        ):
1828
            # Given that artificially abstract bases aren't real `abc.ABC`s and do
1829
            # not annotate their `abstractmethod`s, we can't assert right now that
1830
            # any the type will satisfy the artificially abstract base. However,
1831
            # we can collect any defined methods into a set for artificial bases
1832
            # and assert that any extra methods are included in that set below.
1833
            artificially_abstract.add(interface)
1✔
1834
            artificially_abstract_base_members.update(
1✔
1835
                map(
1836
                    lambda v: v[0],
1837
                    inspect.getmembers(interface_type, predicate=is_member),
1838
                )
1839
            )
1840
            # The meta key :abstract-members will give users the escape hatch to force
1841
            # the compiler to recognize those members as abstract members.
1842
            #
1843
            # When dealing with artificially abstract members, it may be the case
1844
            # that members of superclasses of a specific type don't actually declare
1845
            # abstract member methods expected by subclasses. One particular (major)
1846
            # instance is that of `io.IOBase` which does not declare "read" or "write"
1847
            # as abstract members but whose documentation declares both methods should
1848
            # be considered part of the interface.
1849
            artificially_abstract_base_members.update(
1✔
1850
                __get_artificially_abstract_members(ctx, special_form, interface)
1851
            )
1852
            supertype_possibly_weakref.append(__is_type_weakref(interface_type))
1✔
1853
        elif is_abstract(interface_type):
1✔
1854
            interface_names: FrozenSet[str] = interface_type.__abstractmethods__
1✔
1855
            interface_property_names: FrozenSet[str] = frozenset(
1✔
1856
                method
1857
                for method in interface_names
1858
                if isinstance(getattr(interface_type, method), property)
1859
            )
1860
            interface_method_names = interface_names - interface_property_names
1✔
1861
            if not interface_method_names.issubset(member_names):
1✔
1862
                missing_methods = ", ".join(interface_method_names - member_names)
1✔
1863
                raise ctx.AnalyzerException(
1✔
1864
                    f"{special_form} definition missing interface members for "
1865
                    f"interface {interface.form}: {missing_methods}",
1866
                    form=interface.form,
1867
                    lisp_ast=interface,
1868
                )
1869
            elif not interface_property_names.issubset(all_member_names):
1✔
1870
                missing_fields = ", ".join(interface_property_names - field_names)
1✔
1871
                raise ctx.AnalyzerException(
1✔
1872
                    f"{special_form} definition missing interface properties for "
1873
                    f"interface {interface.form}: {missing_fields}",
1874
                    form=interface.form,
1875
                    lisp_ast=interface,
1876
                )
1877

1878
            all_interface_methods.update(interface_names)
1✔
1879
            supertype_possibly_weakref.append(__is_type_weakref(interface_type))
1✔
1880
        else:
1881
            raise ctx.AnalyzerException(
1✔
1882
                f"{special_form} interface must be Python abstract class or object",
1883
                form=interface.form,
1884
                lisp_ast=interface,
1885
            )
1886

1887
    # We cannot compute if there are extra methods defined if there are any
1888
    # unverifiably abstract bases, so we just skip this check.
1889
    if unverifiably_abstract:
1✔
1890
        logger.warning(
1✔
1891
            f"Unable to verify abstractness for {special_form} supertype(s): "
1892
            f"{', '.join(str(e.var) for e in unverifiably_abstract)}"
1893
        )
1894
    else:
1895
        extra_methods = member_names - all_interface_methods - OBJECT_DUNDER_METHODS
1✔
1896
        if extra_methods and not extra_methods.issubset(
1✔
1897
            artificially_abstract_base_members
1898
        ):
1899
            expected_methods = (
1✔
1900
                all_interface_methods | artificially_abstract_base_members
1901
            ) - OBJECT_DUNDER_METHODS
1902
            expected_method_str = ", ".join(expected_methods)
1✔
1903
            extra_method_str = ", ".join(extra_methods)
1✔
1904
            raise ctx.AnalyzerException(
1✔
1905
                f"{special_form} definition for interface includes members not "
1906
                f"part of defined interfaces: {extra_method_str}; expected one of: "
1907
                f"{expected_method_str}"
1908
            )
1909

1910
    return _TypeAbstractness(
1✔
1911
        is_statically_verified_as_abstract=not unverifiably_abstract,
1912
        artificially_abstract_supertypes=lset.set(artificially_abstract),
1913
        supertype_already_weakref=any(supertype_possibly_weakref),
1914
    )
1915

1916

1917
__DEFTYPE_DEFAULT_SENTINEL = object()
1✔
1918

1919

1920
def _deftype_ast(  # pylint: disable=too-many-locals
1✔
1921
    form: ISeq, ctx: AnalyzerContext
1922
) -> DefType:
1923
    assert form.first == SpecialForm.DEFTYPE
1✔
1924

1925
    nelems = count(form)
1✔
1926
    if nelems < 3:
1✔
1927
        raise ctx.AnalyzerException(
1✔
1928
            "deftype forms must have 3 or more elements, as in: "
1929
            "(deftype* name fields :implements [bases+impls])",
1930
            form=form,
1931
        )
1932

1933
    name = runtime.nth(form, 1)
1✔
1934
    if not isinstance(name, sym.Symbol):
1✔
1935
        raise ctx.AnalyzerException(
1✔
1936
            f"deftype* names must be symbols, not {type(name)}", form=name
1937
        )
1938
    ctx.put_new_symbol(
1✔
1939
        name,
1940
        Binding(
1941
            form=name, name=name.name, local=LocalType.DEFTYPE, env=ctx.get_node_env()
1942
        ),
1943
        warn_if_unused=False,
1944
    )
1945

1946
    fields = runtime.nth(form, 2)
1✔
1947
    if not isinstance(fields, vec.PersistentVector):
1✔
1948
        raise ctx.AnalyzerException(
1✔
1949
            f"deftype* fields must be vector, not {type(fields)}", form=fields
1950
        )
1951

1952
    has_defaults = False
1✔
1953
    with ctx.new_symbol_table(name.name):
1✔
1954
        is_frozen = True
1✔
1955
        param_nodes = []
1✔
1956
        for field in fields:
1✔
1957
            if not isinstance(field, sym.Symbol):
1✔
1958
                raise ctx.AnalyzerException(
1✔
1959
                    "deftype* fields must be symbols", form=field
1960
                )
1961

1962
            field_default = (
1✔
1963
                Maybe(field.meta)
1964
                .map(
1965
                    lambda m: m.val_at(SYM_DEFAULT_META_KEY, __DEFTYPE_DEFAULT_SENTINEL)
1966
                )
1967
                .value
1968
            )
1969
            if not has_defaults and field_default is not __DEFTYPE_DEFAULT_SENTINEL:
1✔
1970
                has_defaults = True
1✔
1971
            elif has_defaults and field_default is __DEFTYPE_DEFAULT_SENTINEL:
1✔
1972
                raise ctx.AnalyzerException(
1✔
1973
                    "deftype* fields without defaults may not appear after fields "
1974
                    "without defaults",
1975
                    form=field,
1976
                )
1977

1978
            is_mutable = _is_mutable(field)
1✔
1979
            if is_mutable:
1✔
1980
                is_frozen = False
1✔
1981

1982
            binding = Binding(
1✔
1983
                form=field,
1984
                name=field.name,
1985
                local=LocalType.FIELD,
1986
                is_assignable=is_mutable,
1987
                env=ctx.get_node_env(),
1988
                tag=_tag_ast(_tag_meta(field), ctx),
1989
                init=(
1990
                    analyze_form(ctx, field_default)
1991
                    if field_default is not __DEFTYPE_DEFAULT_SENTINEL
1992
                    else None
1993
                ),
1994
            )
1995
            param_nodes.append(binding)
1✔
1996
            ctx.put_new_symbol(field, binding, warn_if_unused=False)
1✔
1997

1998
        interfaces, members = __deftype_or_reify_impls(
1✔
1999
            runtime.nthrest(form, 3), ctx, SpecialForm.DEFTYPE
2000
        )
2001
        type_abstractness = __deftype_and_reify_impls_are_all_abstract(
1✔
2002
            ctx, SpecialForm.DEFTYPE, map(lambda f: f.name, fields), interfaces, members
2003
        )
2004
        return DefType(
1✔
2005
            form=form,
2006
            name=name.name,
2007
            interfaces=vec.vector(interfaces),
2008
            fields=vec.vector(param_nodes),
2009
            members=vec.vector(members),
2010
            verified_abstract=type_abstractness.is_statically_verified_as_abstract,
2011
            artificially_abstract=type_abstractness.artificially_abstract_supertypes,
2012
            is_frozen=is_frozen,
2013
            use_weakref_slot=not type_abstractness.supertype_already_weakref,
2014
            env=ctx.get_node_env(pos=ctx.syntax_position),
2015
        )
2016

2017

2018
def _do_ast(form: ISeq, ctx: AnalyzerContext) -> Do:
1✔
2019
    assert form.first == SpecialForm.DO
1✔
2020
    statements, ret = _body_ast(form.rest, ctx)
1✔
2021
    return Do(
1✔
2022
        form=form,
2023
        statements=vec.vector(statements),
2024
        ret=ret,
2025
        use_var_indirection=_is_use_var_indirection(form.first),
2026
        env=ctx.get_node_env(pos=ctx.syntax_position),
2027
    )
2028

2029

2030
def __fn_method_ast(  # pylint: disable=too-many-locals
1✔
2031
    form: ISeq,
2032
    ctx: AnalyzerContext,
2033
    fnname: Optional[sym.Symbol] = None,
2034
    is_async: bool = False,
2035
) -> FnArity:
2036
    with ctx.new_symbol_table("fn-method", is_context_boundary=True):
1✔
2037
        params = form.first
1✔
2038
        if not isinstance(params, vec.PersistentVector):
1✔
2039
            raise ctx.AnalyzerException(
1✔
2040
                "function arity arguments must be a vector", form=params
2041
            )
2042
        return_tag = _tag_ast(_tag_meta(params), ctx)
1✔
2043

2044
        has_vargs, vargs_idx = False, 0
1✔
2045
        param_nodes = []
1✔
2046
        for i, s in enumerate(params):
1✔
2047
            if not isinstance(s, sym.Symbol):
1✔
2048
                raise ctx.AnalyzerException(
1✔
2049
                    "function arity parameter name must be a symbol", form=s
2050
                )
2051

2052
            if s == AMPERSAND:
1✔
2053
                has_vargs = True
1✔
2054
                vargs_idx = i
1✔
2055
                break
1✔
2056

2057
            binding = Binding(
1✔
2058
                form=s,
2059
                name=s.name,
2060
                local=LocalType.ARG,
2061
                tag=_tag_ast(_tag_meta(s), ctx),
2062
                arg_id=i,
2063
                is_variadic=False,
2064
                env=ctx.get_node_env(),
2065
            )
2066
            param_nodes.append(binding)
1✔
2067
            ctx.put_new_symbol(s, binding)
1✔
2068

2069
        if has_vargs:
1✔
2070
            try:
1✔
2071
                vargs_sym = params[vargs_idx + 1]
1✔
2072
                if not isinstance(vargs_sym, sym.Symbol):
1✔
2073
                    raise ctx.AnalyzerException(
1✔
2074
                        "function rest parameter name must be a symbol", form=vargs_sym
2075
                    )
2076

2077
                binding = Binding(
1✔
2078
                    form=vargs_sym,
2079
                    name=vargs_sym.name,
2080
                    local=LocalType.ARG,
2081
                    tag=_tag_ast(_tag_meta(vargs_sym), ctx),
2082
                    arg_id=vargs_idx + 1,
2083
                    is_variadic=True,
2084
                    env=ctx.get_node_env(),
2085
                )
2086
                param_nodes.append(binding)
1✔
2087
                ctx.put_new_symbol(vargs_sym, binding)
1✔
2088
            except IndexError:
1✔
2089
                raise ctx.AnalyzerException(
1✔
2090
                    "Expected variadic argument name after '&'", form=params
2091
                ) from None
2092

2093
        fn_loop_id = genname("fn_arity" if fnname is None else fnname.name)
1✔
2094
        with ctx.new_recur_point(fn_loop_id, param_nodes):
1✔
2095
            with ctx.new_func_ctx(
1✔
2096
                FunctionContext.ASYNC_FUNCTION if is_async else FunctionContext.FUNCTION
2097
            ), ctx.expr_pos():
2098
                stmts, ret = _body_ast(form.rest, ctx)
1✔
2099
            method = FnArity(
1✔
2100
                form=form,
2101
                loop_id=fn_loop_id,
2102
                params=vec.vector(param_nodes),
2103
                tag=return_tag,
2104
                is_variadic=has_vargs,
2105
                fixed_arity=len(param_nodes) - int(has_vargs),
2106
                body=Do(
2107
                    form=form.rest,
2108
                    statements=vec.vector(stmts),
2109
                    ret=ret,
2110
                    is_body=True,
2111
                    # Use the argument vector or first body statement, whichever
2112
                    # exists, for metadata.
2113
                    env=ctx.get_node_env(),
2114
                ),
2115
                # Use the argument vector for fetching line/col since the
2116
                # form itself is a sequence with no meaningful metadata.
2117
                env=ctx.get_node_env(),
2118
            )
2119
            method.visit(partial(_assert_recur_is_tail, ctx))
1✔
2120
            return method
1✔
2121

2122

2123
def __fn_kwargs_support(ctx: AnalyzerContext, o: IMeta) -> Optional[KeywordArgSupport]:
1✔
2124
    if o.meta is None:
1✔
2125
        return None
1✔
2126

2127
    kwarg_support = o.meta.val_at(SYM_KWARGS_META_KEY)
1✔
2128
    if kwarg_support is None:
1✔
2129
        return None
1✔
2130

2131
    try:
1✔
2132
        return KeywordArgSupport(kwarg_support)
1✔
2133
    except ValueError as e:
1✔
2134
        raise ctx.AnalyzerException(
1✔
2135
            "fn keyword argument support metadata :kwarg must be one of: #{:apply :collect}",
2136
            form=kwarg_support,
2137
        ) from e
2138

2139

2140
InlineMeta = Union[Callable, bool, None]
1✔
2141

2142

2143
@functools.singledispatch
1✔
2144
def __unquote_args(f: LispForm, _: FrozenSet[sym.Symbol]):
1✔
2145
    return f
1✔
2146

2147

2148
@__unquote_args.register(sym.Symbol)
1✔
2149
def __unquote_args_sym(f: sym.Symbol, args: FrozenSet[sym.Symbol]):
1✔
2150
    if f in args:
1✔
2151
        return llist.l(reader._UNQUOTE, f)
1✔
2152
    return f
1✔
2153

2154

2155
def _inline_fn_ast(
1✔
2156
    ctx: AnalyzerContext,
2157
    form: Union[llist.PersistentList, ISeq],
2158
    name: Optional[Binding],
2159
    arities: vec.PersistentVector[FnArity],
2160
    num_variadic: int,
2161
) -> Optional[Fn]:
2162
    if not ctx.should_generate_auto_inlines:
1✔
2163
        return None
1✔
2164

2165
    inline_arity = arities[0]
1✔
2166

2167
    if num_variadic != 0:
1✔
2168
        raise ctx.AnalyzerException(
1✔
2169
            "functions with variadic arity may not be inlined",
2170
            form=form,
2171
        )
2172

2173
    if len(arities) != 1:
1✔
2174
        raise ctx.AnalyzerException(
1✔
2175
            "multi-arity functions cannot be inlined",
2176
            form=form,
2177
        )
2178

2179
    if len(inline_arity.body.statements) > 0:
1✔
2180
        raise ctx.AnalyzerException(
1✔
2181
            "cannot generate an inline function for functions with more than one "
2182
            "body expression",
2183
            form=form,
2184
        )
2185

2186
    logger.log(
1✔
2187
        TRACE, f"Generating inline def for {name.name if name is not None else 'fn'}"
2188
    )
2189
    unquoted_form = reader._postwalk(
1✔
2190
        lambda f: __unquote_args(
2191
            f,
2192
            frozenset(binding.form for binding in inline_arity.params),
2193
        ),
2194
        inline_arity.body.ret.form,
2195
    )
2196
    macroed_form = reader.syntax_quote(unquoted_form)
1✔
2197
    inline_fn_form = llist.l(
1✔
2198
        SpecialForm.FN,
2199
        *([sym.symbol(genname(f"{name.name}-inline"))] if name is not None else []),
2200
        vec.vector(binding.form for binding in inline_arity.params),
2201
        macroed_form,
2202
    )
2203
    return _fn_ast(inline_fn_form, ctx)
1✔
2204

2205

2206
@_with_meta  # noqa: MC0001
1✔
2207
def _fn_ast(  # pylint: disable=too-many-locals,too-many-statements
1✔
2208
    form: Union[llist.PersistentList, ISeq], ctx: AnalyzerContext
2209
) -> Fn:
2210
    assert form.first == SpecialForm.FN
1✔
2211

2212
    idx = 1
1✔
2213

2214
    with ctx.new_symbol_table("fn", is_context_boundary=True):
1✔
2215
        try:
1✔
2216
            name = runtime.nth(form, idx)
1✔
2217
        except IndexError as e:
1✔
2218
            raise ctx.AnalyzerException(
1✔
2219
                "fn form must match: (fn* name? [arg*] body*) or (fn* name? method*)",
2220
                form=form,
2221
            ) from e
2222

2223
        name_node: Optional[Binding]
2224
        inline: InlineMeta
2225
        if isinstance(name, sym.Symbol):
1✔
2226
            name_node = Binding(
1✔
2227
                form=name, name=name.name, local=LocalType.FN, env=ctx.get_node_env()
2228
            )
2229
            is_async = _is_async(name) or isinstance(form, IMeta) and _is_async(form)
1✔
2230
            inline = (
1✔
2231
                _inline_meta(name) or isinstance(form, IMeta) and _inline_meta(form)
2232
            )
2233
            kwarg_support = (
1✔
2234
                __fn_kwargs_support(ctx, name)
2235
                or isinstance(form, IMeta)
2236
                and __fn_kwargs_support(ctx, form)
2237
            )
2238
            ctx.put_new_symbol(name, name_node, warn_if_unused=False)
1✔
2239
            idx += 1
1✔
2240
        elif isinstance(name, (llist.PersistentList, vec.PersistentVector)):
1✔
2241
            name = None
1✔
2242
            name_node = None
1✔
2243
            is_async = isinstance(form, IMeta) and _is_async(form)
1✔
2244
            inline = isinstance(form, IMeta) and _inline_meta(form)
1✔
2245
            kwarg_support = isinstance(form, IMeta) and __fn_kwargs_support(ctx, form)
1✔
2246
        else:
2247
            raise ctx.AnalyzerException(
1✔
2248
                "fn form must match: (fn* name? [arg*] body*) or (fn* name? method*)",
2249
                form=form,
2250
            )
2251

2252
        try:
1✔
2253
            arity_or_args = runtime.nth(form, idx)
1✔
2254
        except IndexError as e:
1✔
2255
            raise ctx.AnalyzerException(
1✔
2256
                "fn form expects either multiple arities or a vector of arguments",
2257
                form=form,
2258
            ) from e
2259

2260
        if isinstance(arity_or_args, llist.PersistentList):
1✔
2261
            arities = vec.vector(
1✔
2262
                map(
2263
                    lambda form: __fn_method_ast(
2264
                        form, ctx, fnname=name, is_async=is_async
2265
                    ),
2266
                    runtime.nthrest(form, idx),
2267
                )
2268
            )
2269
        elif isinstance(arity_or_args, vec.PersistentVector):
1✔
2270
            arities = vec.v(
1✔
2271
                __fn_method_ast(
2272
                    runtime.nthrest(form, idx), ctx, fnname=name, is_async=is_async
2273
                )
2274
            )
2275
        else:
2276
            raise ctx.AnalyzerException(
1✔
2277
                "fn form must match: (fn* name? [arg*] body*) or (fn* name? method*)",
2278
                form=form,
2279
            )
2280

2281
        nmethods = count(arities)
1✔
2282
        assert nmethods > 0, "fn must have at least one arity"
1✔
2283

2284
        if kwarg_support is not None and nmethods > 1:
1✔
2285
            raise ctx.AnalyzerException(
1✔
2286
                "multi-arity functions may not declare support for keyword arguments",
2287
                form=form,
2288
            )
2289

2290
        fixed_arities: MutableSet[int] = set()
1✔
2291
        fixed_arity_for_variadic: Optional[int] = None
1✔
2292
        num_variadic = 0
1✔
2293
        for arity in arities:
1✔
2294
            if arity.is_variadic:
1✔
2295
                if num_variadic > 0:
1✔
2296
                    raise ctx.AnalyzerException(
1✔
2297
                        "fn may have at most 1 variadic arity", form=arity.form
2298
                    )
2299
                fixed_arity_for_variadic = arity.fixed_arity
1✔
2300
                num_variadic += 1
1✔
2301
            else:
2302
                if arity.fixed_arity in fixed_arities:
1✔
2303
                    raise ctx.AnalyzerException(
1✔
2304
                        "fn may not have multiple methods with the same fixed arity",
2305
                        form=arity.form,
2306
                    )
2307
                fixed_arities.add(arity.fixed_arity)
1✔
2308

2309
        if fixed_arity_for_variadic is not None and any(
1✔
2310
            fixed_arity_for_variadic < arity for arity in fixed_arities
2311
        ):
2312
            raise ctx.AnalyzerException(
1✔
2313
                "variadic arity may not have fewer fixed arity arguments than any other arities",
2314
                form=form,
2315
            )
2316

2317
        return Fn(
1✔
2318
            form=form,
2319
            is_variadic=num_variadic == 1,
2320
            max_fixed_arity=max(node.fixed_arity for node in arities),
2321
            arities=arities,
2322
            local=name_node,
2323
            env=ctx.get_node_env(pos=ctx.syntax_position),
2324
            is_async=is_async,
2325
            kwarg_support=None if isinstance(kwarg_support, bool) else kwarg_support,
2326
            # Generate an inlineable (macro) variant of the current function if:
2327
            #  - the metadata for the fn contains a boolean `:inline` meta key
2328
            #  - the function has a single arity, composed of a single expression
2329
            #
2330
            # This node attribute (inline_fn) is not used for holding user provided
2331
            # inline functions. If a user provides an inline function, then that
2332
            # function will be compiled as part of the metadata already and will be
2333
            # found by the compiler on the associated Var without needing this code
2334
            # path at all.
2335
            inline_fn=(
2336
                _inline_fn_ast(ctx, form, name_node, arities, num_variadic)
2337
                if isinstance(inline, bool)
2338
                else None
2339
            ),
2340
        )
2341

2342

2343
def _host_call_ast(form: ISeq, ctx: AnalyzerContext) -> HostCall:
1✔
2344
    assert isinstance(form.first, sym.Symbol)
1✔
2345

2346
    method = form.first
1✔
2347
    assert isinstance(method, sym.Symbol), "host interop field must be a symbol"
1✔
2348
    assert method.name.startswith(".")
1✔
2349

2350
    if not count(form) >= 2:
1✔
2351
        raise ctx.AnalyzerException(
1✔
2352
            "host interop calls must be 2 or more elements long", form=form
2353
        )
2354

2355
    args, kwargs = _call_args_ast(runtime.nthrest(form, 2), ctx)
1✔
2356
    return HostCall(
1✔
2357
        form=form,
2358
        method=method.name[1:],
2359
        target=_analyze_form(runtime.nth(form, 1), ctx),
2360
        args=args,
2361
        kwargs=kwargs,
2362
        env=ctx.get_node_env(pos=ctx.syntax_position),
2363
    )
2364

2365

2366
def _host_prop_ast(form: ISeq, ctx: AnalyzerContext) -> HostField:
1✔
2367
    assert isinstance(form.first, sym.Symbol)
1✔
2368

2369
    field = form.first
1✔
2370
    assert isinstance(field, sym.Symbol), "host interop field must be a symbol"
1✔
2371

2372
    nelems = count(form)
1✔
2373
    assert field.name.startswith(".-")
1✔
2374

2375
    if field.name == ".-":
1✔
2376
        try:
1✔
2377
            field = runtime.nth(form, 2)
1✔
2378
        except IndexError as e:
1✔
2379
            raise ctx.AnalyzerException(
1✔
2380
                "host interop prop must be exactly 3 elems long: (.- target field)",
2381
                form=form,
2382
            ) from e
2383
        else:
2384
            if not isinstance(field, sym.Symbol):
1✔
2385
                raise ctx.AnalyzerException(
1✔
2386
                    "host interop field must be a symbol", form=form
2387
                )
2388

2389
        if not nelems == 3:
1✔
2390
            raise ctx.AnalyzerException(
1✔
2391
                "host interop prop must be exactly 3 elems long: (.- target field)",
2392
                form=form,
2393
            )
2394

2395
        return HostField(
1✔
2396
            form=form,
2397
            field=field.name,
2398
            target=_analyze_form(runtime.nth(form, 1), ctx),
2399
            is_assignable=True,
2400
            env=ctx.get_node_env(pos=ctx.syntax_position),
2401
        )
2402
    else:
2403
        if not nelems == 2:
1✔
2404
            raise ctx.AnalyzerException(
1✔
2405
                "host interop prop must be exactly 2 elements long: (.-field target)",
2406
                form=form,
2407
            )
2408

2409
        return HostField(
1✔
2410
            form=form,
2411
            field=field.name[2:],
2412
            target=_analyze_form(runtime.nth(form, 1), ctx),
2413
            is_assignable=True,
2414
            env=ctx.get_node_env(pos=ctx.syntax_position),
2415
        )
2416

2417

2418
def _host_interop_ast(form: ISeq, ctx: AnalyzerContext) -> Union[HostCall, HostField]:
1✔
2419
    assert form.first == SpecialForm.INTEROP_CALL
1✔
2420
    nelems = count(form)
1✔
2421
    assert nelems >= 3
1✔
2422

2423
    maybe_m_or_f = runtime.nth(form, 2)
1✔
2424
    if isinstance(maybe_m_or_f, sym.Symbol):
1✔
2425
        # The clojure.tools.analyzer spec is unclear about whether or not a form
2426
        # like (. target -field) should be emitted as a :host-field or as a
2427
        # :host-interop node. I have elected to emit :host-field, since it is
2428
        # more specific.
2429
        if maybe_m_or_f.name.startswith("-"):
1✔
2430
            if nelems != 3:
1✔
2431
                raise ctx.AnalyzerException(
1✔
2432
                    "host field accesses must be exactly 3 elements long", form=form
2433
                )
2434

2435
            return HostField(
1✔
2436
                form=form,
2437
                field=maybe_m_or_f.name[1:],
2438
                target=_analyze_form(runtime.nth(form, 1), ctx),
2439
                is_assignable=True,
2440
                env=ctx.get_node_env(pos=ctx.syntax_position),
2441
            )
2442
        else:
2443
            args, kwargs = _call_args_ast(runtime.nthrest(form, 3), ctx)
1✔
2444
            return HostCall(
1✔
2445
                form=form,
2446
                method=maybe_m_or_f.name,
2447
                target=_analyze_form(runtime.nth(form, 1), ctx),
2448
                args=args,
2449
                kwargs=kwargs,
2450
                env=ctx.get_node_env(pos=ctx.syntax_position),
2451
            )
2452
    elif isinstance(maybe_m_or_f, (llist.PersistentList, ISeq)):
1✔
2453
        # Likewise, I emit :host-call for forms like (. target (method arg1 ...)).
2454
        method = maybe_m_or_f.first
1✔
2455
        if not isinstance(method, sym.Symbol):
1✔
2456
            raise ctx.AnalyzerException(
1✔
2457
                "host call method must be a symbol", form=method
2458
            )
2459

2460
        args, kwargs = _call_args_ast(maybe_m_or_f.rest, ctx)
1✔
2461
        return HostCall(
1✔
2462
            form=form,
2463
            method=method.name[1:] if method.name.startswith("-") else method.name,
2464
            target=_analyze_form(runtime.nth(form, 1), ctx),
2465
            args=args,
2466
            kwargs=kwargs,
2467
            env=ctx.get_node_env(pos=ctx.syntax_position),
2468
        )
2469
    else:
2470
        raise ctx.AnalyzerException(
1✔
2471
            "host interop forms must take the form: "
2472
            "(. instance (method args*)), "
2473
            "(. instance method args*), "
2474
            "(. instance -field), ",
2475
            form=form,
2476
        )
2477

2478

2479
def _if_ast(form: ISeq, ctx: AnalyzerContext) -> If:
1✔
2480
    assert form.first == SpecialForm.IF
1✔
2481

2482
    nelems = count(form)
1✔
2483
    if nelems not in (3, 4):
1✔
2484
        raise ctx.AnalyzerException(
1✔
2485
            "if forms must have either 3 or 4 elements, as in: (if test then else?)",
2486
            form=form,
2487
        )
2488

2489
    with ctx.expr_pos():
1✔
2490
        test_node = _analyze_form(runtime.nth(form, 1), ctx)
1✔
2491

2492
    with ctx.parent_pos():
1✔
2493
        then_node = _analyze_form(runtime.nth(form, 2), ctx)
1✔
2494

2495
        if nelems == 4:
1✔
2496
            else_node = _analyze_form(runtime.nth(form, 3), ctx)
1✔
2497
        else:
2498
            else_node = _const_node(None, ctx)
1✔
2499

2500
    return If(
1✔
2501
        form=form,
2502
        test=test_node,
2503
        then=then_node,
2504
        else_=else_node,
2505
        env=ctx.get_node_env(pos=ctx.syntax_position),
2506
    )
2507

2508

2509
def _do_warn_on_import_name_clash(
1✔
2510
    ctx: AnalyzerContext, alias_nodes: List[ImportAlias]
2511
) -> None:
2512
    assert alias_nodes, "Must have at least one alias"
1✔
2513

2514
    # Fetch these locally to avoid triggering more locks than we need to
2515
    current_ns = ctx.current_ns
1✔
2516
    aliases, import_aliases, imports = (
1✔
2517
        current_ns.aliases,
2518
        current_ns.import_aliases,
2519
        current_ns.imports,
2520
    )
2521

2522
    def _node_loc(env: NodeEnv) -> str:
1✔
2523
        if env.line is None:
1✔
2524
            return str(env.ns)
1✔
NEW
2525
        return f"{env.ns}:{env.line}"
×
2526

2527
    # Identify duplicates in the import list first
2528
    name_to_nodes = defaultdict(list)
1✔
2529
    for node in alias_nodes:
1✔
2530
        name_to_nodes[(node.alias or node.name)].append(node)
1✔
2531

2532
    for name, nodes in name_to_nodes.items():
1✔
2533
        if len(nodes) < 2:
1✔
2534
            continue
1✔
2535

NEW
2536
        locs = [_node_loc(node.env) for node in nodes]
×
NEW
2537
        logger.warning(
×
2538
            f"duplicate name or alias '{name}' in import ({'; '.join(locs)})"
2539
        )
2540

2541
    # Now check against names in the namespace
2542
    for name, nodes in name_to_nodes.items():
1✔
2543
        name_sym = sym.symbol(name)
1✔
2544
        node, *_ = nodes
1✔
2545

2546
        if name_sym in aliases:
1✔
NEW
2547
            logger.warning(
×
2548
                f"name '{name}' may be shadowed by existing alias in '{current_ns}' "
2549
                f"({_node_loc(node.env)})"
2550
            )
2551
        if name_sym in import_aliases:
1✔
NEW
2552
            logger.warning(
×
2553
                f"name '{name}' may be shadowed by existing import alias in "
2554
                f"'{current_ns}' ({_node_loc(node.env)})"
2555
            )
2556
        if name_sym in imports:
1✔
2557
            logger.warning(
1✔
2558
                f"name '{name}' may be shadowed by existing import in '{current_ns}' "
2559
                f"({_node_loc(node.env)})"
2560
            )
2561

2562

2563
def _import_ast(form: ISeq, ctx: AnalyzerContext) -> Import:
1✔
2564
    assert form.first == SpecialForm.IMPORT
1✔
2565

2566
    aliases = []
1✔
2567
    for f in form.rest:
1✔
2568
        if isinstance(f, sym.Symbol):
1✔
2569
            module_name = f
1✔
2570
            module_alias = None
1✔
2571

2572
            ctx.put_new_symbol(
1✔
2573
                module_name,
2574
                Binding(
2575
                    form=module_name,
2576
                    name=module_name.name,
2577
                    local=LocalType.IMPORT,
2578
                    env=ctx.get_node_env(),
2579
                ),
2580
                symbol_table=ctx.symbol_table.context_boundary,
2581
            )
2582
        elif isinstance(f, vec.PersistentVector):
1✔
2583
            if len(f) != 3:
1✔
2584
                raise ctx.AnalyzerException(
1✔
2585
                    "import alias must take the form: [module :as alias]", form=f
2586
                )
2587
            module_name = f.val_at(0)  # type: ignore[assignment]
1✔
2588
            if not isinstance(module_name, sym.Symbol):
1✔
2589
                raise ctx.AnalyzerException(
1✔
2590
                    "Python module name must be a symbol", form=f
2591
                )
2592
            if not AS == f.val_at(1):
1✔
2593
                raise ctx.AnalyzerException(
1✔
2594
                    "expected :as alias for Python import", form=f
2595
                )
2596
            module_alias_sym = f.val_at(2)
1✔
2597
            if not isinstance(module_alias_sym, sym.Symbol):
1✔
2598
                raise ctx.AnalyzerException(
1✔
2599
                    "Python module alias must be a symbol", form=f
2600
                )
2601
            module_alias = module_alias_sym.name
1✔
2602

2603
            ctx.put_new_symbol(
1✔
2604
                module_alias_sym,
2605
                Binding(
2606
                    form=module_alias_sym,
2607
                    name=module_alias,
2608
                    local=LocalType.IMPORT,
2609
                    env=ctx.get_node_env(),
2610
                ),
2611
                symbol_table=ctx.symbol_table.context_boundary,
2612
            )
2613
        else:
2614
            raise ctx.AnalyzerException("symbol or vector expected for import*", form=f)
1✔
2615

2616
        aliases.append(
1✔
2617
            ImportAlias(
2618
                form=f,
2619
                name=module_name.name,
2620
                alias=module_alias,
2621
                env=ctx.get_node_env(),
2622
            )
2623
        )
2624

2625
    if not aliases:
1✔
2626
        raise ctx.AnalyzerException(
1✔
2627
            "import forms must name at least one module", form=form
2628
        )
2629

2630
    _do_warn_on_import_name_clash(ctx, aliases)
1✔
2631
    return Import(
1✔
2632
        form=form,
2633
        aliases=aliases,
2634
        env=ctx.get_node_env(pos=ctx.syntax_position),
2635
    )
2636

2637

2638
def __handle_macroexpanded_ast(
1✔
2639
    original: Union[llist.PersistentList, ISeq],
2640
    expanded: Union[ReaderForm, ISeq],
2641
    ctx: AnalyzerContext,
2642
) -> Node:
2643
    """Prepare the Lisp AST from macroexpanded and inlined code."""
2644
    if isinstance(expanded, IWithMeta) and isinstance(original, IMeta):
1✔
2645
        old_meta = expanded.meta
1✔
2646
        expanded = expanded.with_meta(
1✔
2647
            old_meta.cons(original.meta) if old_meta else original.meta
2648
        )
2649
    with ctx.expr_pos():
1✔
2650
        expanded_ast = _analyze_form(expanded, ctx)
1✔
2651

2652
    # Verify that macroexpanded code also does not have any non-tail
2653
    # recur forms
2654
    if ctx.recur_point is not None:
1✔
2655
        _assert_recur_is_tail(ctx, expanded_ast)
1✔
2656

2657
    return expanded_ast.assoc(
1✔
2658
        raw_forms=cast(vec.PersistentVector, expanded_ast.raw_forms).cons(original)
2659
    )
2660

2661

2662
def _do_warn_on_arity_mismatch(
1✔
2663
    fn: VarRef, form: Union[llist.PersistentList, ISeq], ctx: AnalyzerContext
2664
) -> None:
2665
    if ctx.warn_on_arity_mismatch and getattr(fn.var.value, "_basilisp_fn", False):
1✔
2666
        arities: Optional[Tuple[Union[int, kw.Keyword]]] = getattr(
1✔
2667
            fn.var.value, "arities", None
2668
        )
2669
        if arities is not None:
1✔
2670
            has_variadic = REST_KW in arities
1✔
2671
            fixed_arities = set(filter(lambda v: v != REST_KW, arities))
1✔
2672
            max_fixed_arity = max(fixed_arities) if fixed_arities else None
1✔
2673
            # This count could be off by 1 for cases where kwargs are being passed,
2674
            # but only Basilisp functions intended to be called by Python code
2675
            # (e.g. with a :kwargs strategy) should ever be called with kwargs,
2676
            # so this seems unlikely enough.
2677
            num_args = runtime.count(form.rest)
1✔
2678
            if has_variadic and (max_fixed_arity is None or num_args > max_fixed_arity):
1✔
2679
                return
1✔
2680
            if num_args not in fixed_arities:
1✔
2681
                report_arities = cast(Set[Union[int, str]], set(fixed_arities))
1✔
2682
                if has_variadic:
1✔
2683
                    report_arities.discard(cast(int, max_fixed_arity))
1✔
2684
                    report_arities.add(f"{max_fixed_arity}+")
1✔
2685
                loc = (
1✔
2686
                    f" ({fn.env.file}:{fn.env.line})"
2687
                    if fn.env.line is not None
2688
                    else f" ({fn.env.file})"
2689
                )
2690
                logger.warning(
1✔
2691
                    f"calling function {fn.var}{loc} with {num_args} arguments; "
2692
                    f"expected any of: {', '.join(sorted(map(str, report_arities)))}",
2693
                )
2694

2695

2696
def _invoke_ast(form: Union[llist.PersistentList, ISeq], ctx: AnalyzerContext) -> Node:
1✔
2697
    with ctx.expr_pos():
1✔
2698
        fn = _analyze_form(form.first, ctx)
1✔
2699

2700
    if fn.op == NodeOp.VAR and isinstance(fn, VarRef):
1✔
2701
        if _is_macro(fn.var) and ctx.should_macroexpand:
1✔
2702
            try:
1✔
2703
                macro_env = ctx.symbol_table.as_env_map()
1✔
2704
                expanded = fn.var.value(macro_env, form, *form.rest)
1✔
2705
                return __handle_macroexpanded_ast(form, expanded, ctx)
1✔
2706
            except Exception as e:
1✔
2707
                if isinstance(e, CompilerException) and (  # pylint: disable=no-member
1✔
2708
                    e.phase in {CompilerPhase.MACROEXPANSION, CompilerPhase.INLINING}
2709
                ):
2710
                    # Do not chain macroexpansion exceptions since they don't
2711
                    # actually add anything of value over the cause exception
2712
                    raise
1✔
2713
                raise CompilerException(
1✔
2714
                    "error occurred during macroexpansion",
2715
                    filename=ctx.filename,
2716
                    form=form,
2717
                    phase=CompilerPhase.MACROEXPANSION,
2718
                ) from e
2719
        elif (
1✔
2720
            ctx.should_inline_functions
2721
            and not (isinstance(form, IMeta) and _is_no_inline(form))
2722
            and fn.var.meta
2723
            and callable(fn.var.meta.get(SYM_INLINE_META_KW))
2724
        ):
2725
            # TODO: also consider whether or not the function(s) inside will be valid
2726
            #       if they are inlined (e.g. if the namespace or module is imported)
2727
            inline_fn = cast(Callable, fn.var.meta.get(SYM_INLINE_META_KW))
1✔
2728
            try:
1✔
2729
                expanded = inline_fn(*form.rest)
1✔
2730
                return __handle_macroexpanded_ast(form, expanded, ctx)
1✔
2731
            except Exception as e:
1✔
2732
                if isinstance(e, CompilerException) and (  # pylint: disable=no-member
1✔
2733
                    e.phase == CompilerPhase.INLINING
2734
                ):
UNCOV
2735
                    raise
×
2736
                raise CompilerException(
1✔
2737
                    "error occurred during inlining",
2738
                    filename=ctx.filename,
2739
                    form=form,
2740
                    phase=CompilerPhase.INLINING,
2741
                ) from e
2742

2743
        _do_warn_on_arity_mismatch(fn, form, ctx)
1✔
2744

2745
    args, kwargs = _call_args_ast(form.rest, ctx)
1✔
2746
    return Invoke(
1✔
2747
        form=form,
2748
        fn=fn,
2749
        args=args,
2750
        kwargs=kwargs,
2751
        env=ctx.get_node_env(pos=ctx.syntax_position),
2752
    )
2753

2754

2755
def _let_ast(form: ISeq, ctx: AnalyzerContext) -> Let:
1✔
2756
    assert form.first == SpecialForm.LET
1✔
2757
    nelems = count(form)
1✔
2758

2759
    if nelems < 2:
1✔
2760
        raise ctx.AnalyzerException(
1✔
2761
            "let forms must have bindings vector and 0 or more body forms", form=form
2762
        )
2763

2764
    bindings = runtime.nth(form, 1)
1✔
2765
    if not isinstance(bindings, vec.PersistentVector):
1✔
2766
        raise ctx.AnalyzerException("let bindings must be a vector", form=bindings)
1✔
2767
    elif len(bindings) % 2 != 0:
1✔
2768
        raise ctx.AnalyzerException(
1✔
2769
            "let bindings must appear in name-value pairs", form=bindings
2770
        )
2771

2772
    with ctx.new_symbol_table("let"):
1✔
2773
        binding_nodes = []
1✔
2774
        for name, value in partition(bindings, 2):
1✔
2775
            if not isinstance(name, sym.Symbol):
1✔
2776
                raise ctx.AnalyzerException(
1✔
2777
                    "let binding name must be a symbol", form=name
2778
                )
2779

2780
            binding = Binding(
1✔
2781
                form=name,
2782
                name=name.name,
2783
                local=LocalType.LET,
2784
                tag=_tag_ast(_tag_meta(name), ctx),
2785
                init=_analyze_form(value, ctx),
2786
                children=vec.v(INIT),
2787
                env=ctx.get_node_env(),
2788
            )
2789
            binding_nodes.append(binding)
1✔
2790
            ctx.put_new_symbol(name, binding)
1✔
2791

2792
        let_body = runtime.nthrest(form, 2)
1✔
2793
        stmts, ret = _body_ast(let_body, ctx)
1✔
2794
        return Let(
1✔
2795
            form=form,
2796
            bindings=vec.vector(binding_nodes),
2797
            body=Do(
2798
                form=let_body,
2799
                statements=vec.vector(stmts),
2800
                ret=ret,
2801
                is_body=True,
2802
                env=ctx.get_node_env(),
2803
            ),
2804
            env=ctx.get_node_env(pos=ctx.syntax_position),
2805
        )
2806

2807

2808
def __letfn_fn_body(form: ISeq, ctx: AnalyzerContext) -> Fn:
1✔
2809
    """Produce an `Fn` node for a `letfn*` special form.
2810

2811
    `letfn*` forms use `let*`-like bindings. Each function binding name is
2812
    added to the symbol table as a forward declaration before analyzing the
2813
    function body. The function bodies are defined as
2814

2815
        (fn* name
2816
          [...]
2817
          ...)
2818

2819
    When the `name` is added to the symbol table for the function, a warning
2820
    will be produced because it will previously have been defined in the
2821
    `letfn*` binding scope. This function adds `:no-warn-on-shadow` metadata to
2822
    the function name symbol to disable the compiler warning."""
2823
    fn_sym = form.first
1✔
2824

2825
    fn_name = runtime.nth(form, 1)
1✔
2826
    if not isinstance(fn_name, sym.Symbol):
1✔
2827
        raise ctx.AnalyzerException(
1✔
2828
            "letfn function name must be a symbol", form=fn_name
2829
        )
2830

2831
    fn_rest = runtime.nthrest(form, 2)
1✔
2832

2833
    fn_body = _analyze_form(
1✔
2834
        fn_rest.cons(
2835
            fn_name.with_meta(
2836
                (fn_name.meta or lmap.PersistentMap.empty()).assoc(
2837
                    SYM_NO_WARN_ON_SHADOW_META_KEY, True
2838
                )
2839
            )
2840
        ).cons(fn_sym),
2841
        ctx,
2842
    )
2843

2844
    if not isinstance(fn_body, Fn):
1✔
2845
        raise ctx.AnalyzerException(
1✔
2846
            "letfn bindings must be functions", form=form, lisp_ast=fn_body
2847
        )
2848

2849
    return fn_body
1✔
2850

2851

2852
def _letfn_ast(  # pylint: disable=too-many-locals
1✔
2853
    form: ISeq, ctx: AnalyzerContext
2854
) -> LetFn:
2855
    assert form.first == SpecialForm.LETFN
1✔
2856
    nelems = count(form)
1✔
2857

2858
    if nelems < 2:
1✔
2859
        raise ctx.AnalyzerException(
1✔
2860
            "letfn forms must have bindings vector and 0 or more body forms", form=form
2861
        )
2862

2863
    bindings = runtime.nth(form, 1)
1✔
2864
    if not isinstance(bindings, vec.PersistentVector):
1✔
2865
        raise ctx.AnalyzerException("letfn bindings must be a vector", form=bindings)
1✔
2866
    elif len(bindings) % 2 != 0:
1✔
2867
        raise ctx.AnalyzerException(
1✔
2868
            "letfn bindings must appear in name-value pairs", form=bindings
2869
        )
2870

2871
    with ctx.new_symbol_table("letfn"):
1✔
2872
        # Generate empty Binding nodes to put into the symbol table
2873
        # as forward declarations. All functions in letfn* forms may
2874
        # refer to all other functions regardless of order of definition.
2875
        empty_binding_nodes = []
1✔
2876
        for name, value in partition(bindings, 2):
1✔
2877
            if not isinstance(name, sym.Symbol):
1✔
2878
                raise ctx.AnalyzerException(
1✔
2879
                    "letfn binding name must be a symbol", form=name
2880
                )
2881

2882
            if not isinstance(value, llist.PersistentList):
1✔
2883
                raise ctx.AnalyzerException(
1✔
2884
                    "letfn binding value must be a list", form=value
2885
                )
2886

2887
            binding = Binding(
1✔
2888
                form=name,
2889
                name=name.name,
2890
                local=LocalType.LETFN,
2891
                init=_const_node(None, ctx),
2892
                children=vec.v(INIT),
2893
                env=ctx.get_node_env(),
2894
            )
2895
            empty_binding_nodes.append((name, value, binding))
1✔
2896
            ctx.put_new_symbol(
1✔
2897
                name,
2898
                binding,
2899
            )
2900

2901
        # Once we've generated all of the filler Binding nodes, analyze the
2902
        # function bodies and replace the Binding nodes with full nodes.
2903
        binding_nodes = []
1✔
2904
        for fn_name, fn_def, binding in empty_binding_nodes:
1✔
2905
            fn_body = __letfn_fn_body(fn_def, ctx)
1✔
2906
            new_binding = binding.assoc(init=fn_body)
1✔
2907
            binding_nodes.append(new_binding)
1✔
2908
            ctx.put_new_symbol(
1✔
2909
                fn_name,
2910
                new_binding,
2911
                warn_on_shadowed_name=False,
2912
                warn_on_shadowed_var=False,
2913
            )
2914

2915
        letfn_body = runtime.nthrest(form, 2)
1✔
2916
        stmts, ret = _body_ast(letfn_body, ctx)
1✔
2917
        return LetFn(
1✔
2918
            form=form,
2919
            bindings=vec.vector(binding_nodes),
2920
            body=Do(
2921
                form=letfn_body,
2922
                statements=vec.vector(stmts),
2923
                ret=ret,
2924
                is_body=True,
2925
                env=ctx.get_node_env(),
2926
            ),
2927
            env=ctx.get_node_env(pos=ctx.syntax_position),
2928
        )
2929

2930

2931
def _loop_ast(form: ISeq, ctx: AnalyzerContext) -> Loop:
1✔
2932
    assert form.first == SpecialForm.LOOP
1✔
2933
    nelems = count(form)
1✔
2934

2935
    if nelems < 2:
1✔
2936
        raise ctx.AnalyzerException(
1✔
2937
            "loop forms must have bindings vector and 0 or more body forms", form=form
2938
        )
2939

2940
    bindings = runtime.nth(form, 1)
1✔
2941
    if not isinstance(bindings, vec.PersistentVector):
1✔
2942
        raise ctx.AnalyzerException("loop bindings must be a vector", form=bindings)
1✔
2943
    elif len(bindings) % 2 != 0:
1✔
2944
        raise ctx.AnalyzerException(
1✔
2945
            "loop bindings must appear in name-value pairs", form=bindings
2946
        )
2947

2948
    loop_id = genname("loop")
1✔
2949
    with ctx.new_symbol_table(loop_id):
1✔
2950
        binding_nodes = []
1✔
2951
        for name, value in partition(bindings, 2):
1✔
2952
            if not isinstance(name, sym.Symbol):
1✔
2953
                raise ctx.AnalyzerException(
1✔
2954
                    "loop binding name must be a symbol", form=name
2955
                )
2956

2957
            binding = Binding(
1✔
2958
                form=name,
2959
                name=name.name,
2960
                local=LocalType.LOOP,
2961
                init=_analyze_form(value, ctx),
2962
                env=ctx.get_node_env(),
2963
            )
2964
            binding_nodes.append(binding)
1✔
2965
            ctx.put_new_symbol(name, binding)
1✔
2966

2967
        with ctx.new_recur_point(loop_id, binding_nodes):
1✔
2968
            loop_body = runtime.nthrest(form, 2)
1✔
2969
            stmts, ret = _body_ast(loop_body, ctx)
1✔
2970
            loop_node = Loop(
1✔
2971
                form=form,
2972
                bindings=vec.vector(binding_nodes),
2973
                body=Do(
2974
                    form=loop_body,
2975
                    statements=vec.vector(stmts),
2976
                    ret=ret,
2977
                    is_body=True,
2978
                    env=ctx.get_node_env(),
2979
                ),
2980
                loop_id=loop_id,
2981
                env=ctx.get_node_env(pos=ctx.syntax_position),
2982
            )
2983
            loop_node.visit(partial(_assert_recur_is_tail, ctx))
1✔
2984
            return loop_node
1✔
2985

2986

2987
def _quote_ast(form: ISeq, ctx: AnalyzerContext) -> Quote:
1✔
2988
    assert form.first == SpecialForm.QUOTE
1✔
2989
    nelems = count(form)
1✔
2990

2991
    if nelems != 2:
1✔
2992
        raise ctx.AnalyzerException(
1✔
2993
            "quote forms must have exactly two elements: (quote form)", form=form
2994
        )
2995

2996
    with ctx.quoted():
1✔
2997
        with ctx.expr_pos():
1✔
2998
            expr = _analyze_form(runtime.nth(form, 1), ctx)
1✔
2999
        assert isinstance(expr, Const), "Quoted expressions must yield :const nodes"
1✔
3000
        return Quote(
1✔
3001
            form=form,
3002
            expr=expr,
3003
            is_literal=True,
3004
            env=ctx.get_node_env(pos=ctx.syntax_position),
3005
        )
3006

3007

3008
def _assert_no_recur(ctx: AnalyzerContext, node: Node) -> None:
1✔
3009
    """Assert that `recur` forms do not appear in any position of this or
3010
    child AST nodes."""
3011
    if node.op == NodeOp.RECUR:
1✔
3012
        raise ctx.AnalyzerException(
1✔
3013
            "recur must appear in tail position", form=node.form, lisp_ast=node
3014
        )
3015
    elif node.op in {NodeOp.FN, NodeOp.LOOP}:
1✔
3016
        pass
1✔
3017
    else:
3018
        node.visit(partial(_assert_no_recur, ctx))
1✔
3019

3020

3021
def _assert_recur_is_tail(ctx: AnalyzerContext, node: Node) -> None:
1✔
3022
    """Assert that `recur` forms only appear in the tail position of this
3023
    or child AST nodes.
3024

3025
    `recur` forms may only appear in `do` nodes (both literal and synthetic
3026
    `do` nodes) and in either the :then or :else expression of an `if` node."""
3027
    if node.op == NodeOp.DO:
1✔
3028
        assert isinstance(node, Do)
1✔
3029
        for child in node.statements:
1✔
3030
            _assert_no_recur(ctx, child)
1✔
3031
        _assert_recur_is_tail(ctx, node.ret)
1✔
3032
    elif node.op in {
1✔
3033
        NodeOp.FN,
3034
        NodeOp.FN_ARITY,
3035
        NodeOp.DEFTYPE_METHOD,
3036
        NodeOp.DEFTYPE_METHOD_ARITY,
3037
    }:
3038
        assert isinstance(node, (Fn, FnArity, DefTypeMethod, DefTypeMethodArity))
1✔
3039
        node.visit(partial(_assert_recur_is_tail, ctx))
1✔
3040
    elif node.op == NodeOp.IF:
1✔
3041
        assert isinstance(node, If)
1✔
3042
        _assert_no_recur(ctx, node.test)
1✔
3043
        _assert_recur_is_tail(ctx, node.then)
1✔
3044
        _assert_recur_is_tail(ctx, node.else_)
1✔
3045
    elif node.op in {NodeOp.LET, NodeOp.LETFN}:
1✔
3046
        assert isinstance(node, (Let, LetFn))
1✔
3047
        for binding in node.bindings:
1✔
3048
            assert binding.init is not None
1✔
3049
            _assert_no_recur(ctx, binding.init)
1✔
3050
        _assert_recur_is_tail(ctx, node.body)
1✔
3051
    elif node.op == NodeOp.LOOP:
1✔
3052
        assert isinstance(node, Loop)
1✔
3053
        for binding in node.bindings:
1✔
3054
            assert binding.init is not None
1✔
3055
            _assert_no_recur(ctx, binding.init)
1✔
3056
    elif node.op == NodeOp.RECUR:
1✔
3057
        pass
1✔
3058
    elif node.op == NodeOp.REIFY:
1✔
3059
        assert isinstance(node, Reify)
1✔
3060
        for child in node.members:
1✔
3061
            _assert_recur_is_tail(ctx, child)
1✔
3062
    elif node.op == NodeOp.TRY:
1✔
3063
        assert isinstance(node, Try)
1✔
3064
        _assert_recur_is_tail(ctx, node.body)
1✔
3065
        for catch in node.catches:
1✔
3066
            _assert_recur_is_tail(ctx, catch)
1✔
3067
        if node.finally_:
1✔
3068
            _assert_no_recur(ctx, node.finally_)
1✔
3069
    else:
3070
        node.visit(partial(_assert_no_recur, ctx))
1✔
3071

3072

3073
def _recur_ast(form: ISeq, ctx: AnalyzerContext) -> Recur:
1✔
3074
    assert form.first == SpecialForm.RECUR
1✔
3075

3076
    if ctx.recur_point is None:
1✔
3077
        if ctx.should_allow_unresolved_symbols:
1✔
3078
            loop_id = genname("macroexpand-recur")
1✔
3079
        else:
3080
            raise ctx.AnalyzerException("no recur point defined for recur", form=form)
1✔
3081
    else:
3082
        if len(ctx.recur_point.args) != count(form.rest):
1✔
3083
            raise ctx.AnalyzerException(
1✔
3084
                "recur arity does not match last recur point arity", form=form
3085
            )
3086

3087
        loop_id = ctx.recur_point.loop_id
1✔
3088

3089
    with ctx.expr_pos():
1✔
3090
        exprs = vec.vector(map(lambda form: _analyze_form(form, ctx), form.rest))
1✔
3091

3092
    return Recur(form=form, exprs=exprs, loop_id=loop_id, env=ctx.get_node_env())
1✔
3093

3094

3095
@_with_meta
1✔
3096
def _reify_ast(form: ISeq, ctx: AnalyzerContext) -> Reify:
1✔
3097
    assert form.first == SpecialForm.REIFY
1✔
3098

3099
    nelems = count(form)
1✔
3100
    if nelems < 3:
1✔
3101
        raise ctx.AnalyzerException(
1✔
3102
            "reify forms must have 3 or more elements, as in: "
3103
            "(reify* :implements [bases+impls])",
3104
            form=form,
3105
        )
3106

3107
    with ctx.new_symbol_table("reify"):
1✔
3108
        interfaces, members = __deftype_or_reify_impls(
1✔
3109
            runtime.nthrest(form, 1), ctx, SpecialForm.REIFY
3110
        )
3111
        type_abstractness = __deftype_and_reify_impls_are_all_abstract(
1✔
3112
            ctx, SpecialForm.REIFY, (), interfaces, members
3113
        )
3114
        return Reify(
1✔
3115
            form=form,
3116
            interfaces=vec.vector(interfaces),
3117
            members=vec.vector(members),
3118
            verified_abstract=type_abstractness.is_statically_verified_as_abstract,
3119
            artificially_abstract=type_abstractness.artificially_abstract_supertypes,
3120
            use_weakref_slot=not type_abstractness.supertype_already_weakref,
3121
            env=ctx.get_node_env(pos=ctx.syntax_position),
3122
        )
3123

3124

3125
def _require_ast(form: ISeq, ctx: AnalyzerContext) -> Require:
1✔
3126
    assert form.first == SpecialForm.REQUIRE
1✔
3127

3128
    aliases = []
1✔
3129
    for f in form.rest:
1✔
3130
        if isinstance(f, sym.Symbol):
1✔
3131
            module_name = f
1✔
3132
            module_alias = None
1✔
3133
        elif isinstance(f, vec.PersistentVector):
1✔
3134
            if len(f) != 3:
1✔
3135
                raise ctx.AnalyzerException(
1✔
3136
                    "require alias must take the form: [namespace :as alias]", form=f
3137
                )
3138
            module_name = f.val_at(0)  # type: ignore[assignment]
1✔
3139
            if not isinstance(module_name, sym.Symbol):
1✔
3140
                raise ctx.AnalyzerException(
1✔
3141
                    "Basilisp namespace name must be a symbol", form=f
3142
                )
3143
            if not AS == f.val_at(1):
1✔
3144
                raise ctx.AnalyzerException(
1✔
3145
                    "expected :as alias for Basilisp alias", form=f
3146
                )
3147
            module_alias_sym = f.val_at(2)
1✔
3148
            if not isinstance(module_alias_sym, sym.Symbol):
1✔
3149
                raise ctx.AnalyzerException(
1✔
3150
                    "Basilisp namespace alias must be a symbol", form=f
3151
                )
3152
            module_alias = module_alias_sym.name
1✔
3153
        else:
3154
            raise ctx.AnalyzerException(
1✔
3155
                "symbol or vector expected for require*", form=f
3156
            )
3157

3158
        aliases.append(
1✔
3159
            RequireAlias(
3160
                form=f,
3161
                name=module_name.name,
3162
                alias=module_alias,
3163
                env=ctx.get_node_env(),
3164
            )
3165
        )
3166

3167
    if not aliases:
1✔
3168
        raise ctx.AnalyzerException(
1✔
3169
            "require forms must name at least one namespace", form=form
3170
        )
3171

3172
    return Require(
1✔
3173
        form=form,
3174
        aliases=aliases,
3175
        env=ctx.get_node_env(pos=ctx.syntax_position),
3176
    )
3177

3178

3179
def _set_bang_ast(form: ISeq, ctx: AnalyzerContext) -> SetBang:
1✔
3180
    assert form.first == SpecialForm.SET_BANG
1✔
3181
    nelems = count(form)
1✔
3182

3183
    if nelems != 3:
1✔
3184
        raise ctx.AnalyzerException(
1✔
3185
            "set! forms must contain exactly 3 elements: (set! target value)", form=form
3186
        )
3187

3188
    with ctx.expr_pos():
1✔
3189
        target = _analyze_form(runtime.nth(form, 1), ctx)
1✔
3190

3191
    if not isinstance(target, Assignable):
1✔
3192
        raise ctx.AnalyzerException(
1✔
3193
            f"cannot set! targets of type {type(target)}", form=target
3194
        )
3195

3196
    if not target.is_assignable:
1✔
3197
        raise ctx.AnalyzerException(
1✔
3198
            "cannot set! target which is not assignable",
3199
            form=form,
3200
            lisp_ast=cast(Node, target),
3201
        )
3202

3203
    # Vars may only be set if they are (1) dynamic, and (2) already have a thread
3204
    # binding established via the `binding` macro in Basilisp or by manually pushing
3205
    # thread bindings (e.g. by runtime.push_thread_bindings).
3206
    #
3207
    # We can (generally) establish statically whether a Var is dynamic at compile time,
3208
    # but establishing whether or not a Var will be thread-bound by the time a set!
3209
    # is executed is much more challenging. Given the dynamic nature of the language,
3210
    # it is simply easier to emit warnings for these potentially invalid cases to let
3211
    # users fix the problem.
3212
    if isinstance(target, VarRef):
1✔
3213
        if ctx.warn_on_non_dynamic_set and not target.var.dynamic:
1✔
3214
            logger.warning(f"set! target {target.var} is not marked as dynamic")
1✔
3215
        elif not target.var.is_thread_bound:
1✔
3216
            # This case is way more likely to result in noise, so just emit at debug.
3217
            logger.debug(f"set! target {target.var} is not marked as thread-bound")
1✔
3218

3219
    with ctx.expr_pos():
1✔
3220
        val = _analyze_form(runtime.nth(form, 2), ctx)
1✔
3221

3222
    return SetBang(
1✔
3223
        form=form,
3224
        target=target,
3225
        val=val,
3226
        env=ctx.get_node_env(pos=ctx.syntax_position),
3227
    )
3228

3229

3230
def _throw_ast(form: ISeq, ctx: AnalyzerContext) -> Throw:
1✔
3231
    assert form.first == SpecialForm.THROW
1✔
3232
    nelems = count(form)
1✔
3233

3234
    if nelems < 2 or nelems > 3:
1✔
3235
        raise ctx.AnalyzerException(
1✔
3236
            "throw forms must contain exactly 2 or 3 elements: (throw exc [cause])",
3237
            form=form,
3238
        )
3239

3240
    with ctx.expr_pos():
1✔
3241
        exc = _analyze_form(runtime.nth(form, 1), ctx)
1✔
3242

3243
    if nelems == 3:
1✔
3244
        with ctx.expr_pos():
1✔
3245
            cause = _analyze_form(runtime.nth(form, 2), ctx)
1✔
3246
    else:
3247
        cause = None
1✔
3248

3249
    return Throw(
1✔
3250
        form=form,
3251
        exception=exc,
3252
        cause=cause,
3253
        env=ctx.get_node_env(pos=ctx.syntax_position),
3254
    )
3255

3256

3257
def _catch_ast(form: ISeq, ctx: AnalyzerContext) -> Catch:
1✔
3258
    assert form.first == SpecialForm.CATCH
1✔
3259
    nelems = count(form)
1✔
3260

3261
    if nelems < 4:
1✔
3262
        raise ctx.AnalyzerException(
1✔
3263
            "catch forms must contain at least 4 elements: (catch class local body*)",
3264
            form=form,
3265
        )
3266

3267
    catch_cls = _analyze_form(runtime.nth(form, 1), ctx)
1✔
3268
    if not isinstance(catch_cls, (MaybeClass, MaybeHostForm)):
1✔
3269
        raise ctx.AnalyzerException(
1✔
3270
            "catch forms must name a class type to catch", form=catch_cls
3271
        )
3272

3273
    local_name = runtime.nth(form, 2)
1✔
3274
    if not isinstance(local_name, sym.Symbol):
1✔
3275
        raise ctx.AnalyzerException("catch local must be a symbol", form=local_name)
1✔
3276

3277
    with ctx.new_symbol_table("catch"):
1✔
3278
        catch_binding = Binding(
1✔
3279
            form=local_name,
3280
            name=local_name.name,
3281
            local=LocalType.CATCH,
3282
            env=ctx.get_node_env(),
3283
        )
3284
        ctx.put_new_symbol(local_name, catch_binding)
1✔
3285

3286
        catch_body = runtime.nthrest(form, 3)
1✔
3287
        catch_statements, catch_ret = _body_ast(catch_body, ctx)
1✔
3288
        return Catch(
1✔
3289
            form=form,
3290
            class_=catch_cls,
3291
            local=catch_binding,
3292
            body=Do(
3293
                form=catch_body,
3294
                statements=vec.vector(catch_statements),
3295
                ret=catch_ret,
3296
                is_body=True,
3297
                env=ctx.get_node_env(),
3298
            ),
3299
            env=ctx.get_node_env(),
3300
        )
3301

3302

3303
def _try_ast(form: ISeq, ctx: AnalyzerContext) -> Try:
1✔
3304
    assert form.first == SpecialForm.TRY
1✔
3305

3306
    try_exprs = []
1✔
3307
    catches = []
1✔
3308
    finally_: Optional[Do] = None
1✔
3309
    for expr in form.rest:
1✔
3310
        if isinstance(expr, (llist.PersistentList, ISeq)):
1✔
3311
            if expr.first == SpecialForm.CATCH:
1✔
3312
                if finally_:
1✔
3313
                    raise ctx.AnalyzerException(
1✔
3314
                        "catch forms may not appear after finally forms in a try",
3315
                        form=expr,
3316
                    )
3317
                catches.append(_catch_ast(expr, ctx))
1✔
3318
                continue
1✔
3319
            elif expr.first == SpecialForm.FINALLY:
1✔
3320
                if finally_ is not None:
1✔
3321
                    raise ctx.AnalyzerException(
1✔
3322
                        "try forms may not contain multiple finally forms", form=expr
3323
                    )
3324
                # Finally values are never returned
3325
                with ctx.stmt_pos():
1✔
3326
                    *finally_stmts, finally_ret = map(
1✔
3327
                        lambda form: _analyze_form(form, ctx), expr.rest
3328
                    )
3329
                finally_ = Do(
1✔
3330
                    form=expr.rest,
3331
                    statements=vec.vector(finally_stmts),
3332
                    ret=finally_ret,
3333
                    is_body=True,
3334
                    env=ctx.get_node_env(
3335
                        pos=NodeSyntacticPosition.STMT,
3336
                    ),
3337
                )
3338
                continue
1✔
3339

3340
        lisp_node = _analyze_form(expr, ctx)
1✔
3341

3342
        if catches:
1✔
3343
            raise ctx.AnalyzerException(
1✔
3344
                "try body expressions may not appear after catch forms", form=expr
3345
            )
3346
        if finally_:
1✔
3347
            raise ctx.AnalyzerException(
1✔
3348
                "try body expressions may not appear after finally forms", form=expr
3349
            )
3350

3351
        try_exprs.append(lisp_node)
1✔
3352

3353
    assert all(
1✔
3354
        isinstance(node, Catch) for node in catches
3355
    ), "All catch statements must be catch ops"
3356

3357
    *try_statements, try_ret = try_exprs
1✔
3358
    return Try(
1✔
3359
        form=form,
3360
        body=Do(
3361
            form=form,
3362
            statements=vec.vector(try_statements),
3363
            ret=try_ret,
3364
            is_body=True,
3365
            env=ctx.get_node_env(pos=ctx.syntax_position),
3366
        ),
3367
        catches=vec.vector(catches),
3368
        finally_=finally_,
3369
        children=(
3370
            vec.v(BODY, CATCHES, FINALLY)
3371
            if finally_ is not None
3372
            else vec.v(BODY, CATCHES)
3373
        ),
3374
        env=ctx.get_node_env(pos=ctx.syntax_position),
3375
    )
3376

3377

3378
def _var_ast(form: ISeq, ctx: AnalyzerContext) -> VarRef:
1✔
3379
    assert form.first == SpecialForm.VAR
1✔
3380

3381
    nelems = count(form)
1✔
3382
    if nelems != 2:
1✔
3383
        raise ctx.AnalyzerException(
1✔
3384
            "var special forms must contain 2 elements: (var sym)", form=form
3385
        )
3386

3387
    var_sym = runtime.nth(form, 1)
1✔
3388
    if not isinstance(var_sym, sym.Symbol):
1✔
3389
        raise ctx.AnalyzerException("vars may only be resolved for symbols", form=form)
1✔
3390

3391
    if var_sym.ns is None:
1✔
3392
        var = runtime.resolve_var(sym.symbol(var_sym.name, ctx.current_ns.name))
1✔
3393
    else:
3394
        var = runtime.resolve_var(var_sym)
1✔
3395

3396
    if var is None:
1✔
3397
        raise ctx.AnalyzerException(f"cannot resolve var {var_sym}", form=form)
1✔
3398

3399
    return VarRef(
1✔
3400
        form=form,
3401
        var=var,
3402
        return_var=True,
3403
        env=ctx.get_node_env(pos=ctx.syntax_position),
3404
    )
3405

3406

3407
def _yield_ast(form: ISeq, ctx: AnalyzerContext) -> Yield:
1✔
3408
    assert form.first == SpecialForm.YIELD
1✔
3409

3410
    if ctx.func_ctx is None:
1✔
3411
        raise ctx.AnalyzerException(
1✔
3412
            "yield forms may not appear in function context", form=form
3413
        )
3414

3415
    nelems = count(form)
1✔
3416
    if nelems not in {1, 2}:
1✔
3417
        raise ctx.AnalyzerException(
1✔
3418
            "yield forms must contain 1 or 2 elements, as in: (yield [expr])", form=form
3419
        )
3420

3421
    if nelems == 2:
1✔
3422
        with ctx.expr_pos():
1✔
3423
            expr = _analyze_form(runtime.nth(form, 1), ctx)
1✔
3424
        return Yield(
1✔
3425
            form=form,
3426
            expr=expr,
3427
            env=ctx.get_node_env(pos=ctx.syntax_position),
3428
        )
3429
    else:
3430
        return Yield.expressionless(form, ctx.get_node_env(pos=ctx.syntax_position))
1✔
3431

3432

3433
SpecialFormHandler = Callable[[T_form, AnalyzerContext], SpecialFormNode]
1✔
3434
_SPECIAL_FORM_HANDLERS: Mapping[sym.Symbol, SpecialFormHandler] = {
1✔
3435
    SpecialForm.AWAIT: _await_ast,
3436
    SpecialForm.DEF: _def_ast,
3437
    SpecialForm.DO: _do_ast,
3438
    SpecialForm.DEFTYPE: _deftype_ast,
3439
    SpecialForm.FN: _fn_ast,
3440
    SpecialForm.IF: _if_ast,
3441
    SpecialForm.IMPORT: _import_ast,
3442
    SpecialForm.INTEROP_CALL: _host_interop_ast,
3443
    SpecialForm.LET: _let_ast,
3444
    SpecialForm.LETFN: _letfn_ast,
3445
    SpecialForm.LOOP: _loop_ast,
3446
    SpecialForm.QUOTE: _quote_ast,
3447
    SpecialForm.RECUR: _recur_ast,
3448
    SpecialForm.REIFY: _reify_ast,
3449
    SpecialForm.REQUIRE: _require_ast,
3450
    SpecialForm.SET_BANG: _set_bang_ast,
3451
    SpecialForm.THROW: _throw_ast,
3452
    SpecialForm.TRY: _try_ast,
3453
    SpecialForm.VAR: _var_ast,
3454
    SpecialForm.YIELD: _yield_ast,
3455
}
3456

3457

3458
##################
3459
# Data Structures
3460
##################
3461

3462

3463
@_analyze_form.register(llist.PersistentList)
1✔
3464
@_analyze_form.register(ISeq)
1✔
3465
@_with_loc
1✔
3466
def _list_node(form: ISeq, ctx: AnalyzerContext) -> Node:
1✔
3467
    if form == llist.PersistentList.empty():
1✔
3468
        with ctx.quoted():
1✔
3469
            return _const_node(form, ctx)
1✔
3470

3471
    if ctx.is_quoted:
1✔
3472
        return _const_node(form, ctx)
1✔
3473

3474
    s = form.first
1✔
3475
    if isinstance(s, sym.Symbol):
1✔
3476
        handle_special_form = _SPECIAL_FORM_HANDLERS.get(s)
1✔
3477
        if handle_special_form is not None:
1✔
3478
            return handle_special_form(form, ctx)
1✔
3479
        elif s.name.startswith(".-"):
1✔
3480
            return _host_prop_ast(form, ctx)
1✔
3481
        elif s.name.startswith(".") and s.name != _DOUBLE_DOT_MACRO_NAME:
1✔
3482
            return _host_call_ast(form, ctx)
1✔
3483

3484
    return _invoke_ast(form, ctx)
1✔
3485

3486

3487
def _resolve_nested_symbol(ctx: AnalyzerContext, form: sym.Symbol) -> HostField:
1✔
3488
    """Resolve an attribute by recursively accessing the parent object
3489
    as if it were its own namespaced symbol."""
3490
    assert form.ns is not None
1✔
3491
    assert "." in form.ns
1✔
3492

3493
    parent_ns, parent_name = form.ns.rsplit(".", maxsplit=1)
1✔
3494
    parent = sym.symbol(parent_name, ns=parent_ns)
1✔
3495
    parent_node = __resolve_namespaced_symbol(ctx, parent)
1✔
3496

3497
    return HostField(
1✔
3498
        form=form,
3499
        field=form.name,
3500
        target=parent_node,
3501
        is_assignable=True,
3502
        env=ctx.get_node_env(pos=ctx.syntax_position),
3503
    )
3504

3505

3506
def __resolve_namespaced_symbol_in_ns(
1✔
3507
    ctx: AnalyzerContext,
3508
    which_ns: runtime.Namespace,
3509
    form: sym.Symbol,
3510
) -> Optional[Union[MaybeHostForm, VarRef]]:
3511
    """Resolve the symbol `form` in the context of the Namespace `which_ns`. If
3512
    `allow_fuzzy_macroexpansion_matching` is True and no match is made on existing
3513
    imports, import aliases, or namespace aliases, then attempt to match the
3514
    namespace portion"""
3515
    assert form.ns is not None
1✔
3516

3517
    ns_sym = sym.symbol(form.ns)
1✔
3518
    if ns_sym in which_ns.imports or ns_sym in which_ns.import_aliases:
1✔
3519
        # Fetch the full namespace name for the aliased namespace/module.
3520
        # We don't need this for actually generating the link later, but
3521
        # we _do_ need it for fetching a reference to the module to check
3522
        # for membership.
3523
        if ns_sym in which_ns.import_aliases:
1✔
3524
            ns = which_ns.import_aliases[ns_sym]
1✔
3525
            assert ns is not None
1✔
3526
            ns_name = ns.name
1✔
3527
        else:
3528
            ns_name = ns_sym.name
1✔
3529

3530
        safe_module_name = munge(ns_name)
1✔
3531
        assert (
1✔
3532
            safe_module_name in sys.modules
3533
        ), f"Module '{safe_module_name}' is not imported"
3534
        ns_module = sys.modules[safe_module_name]
1✔
3535
        safe_name = munge(form.name)
1✔
3536

3537
        # Try without allowing builtins first
3538
        if safe_name in vars(ns_module):
1✔
3539
            return MaybeHostForm(
1✔
3540
                form=form,
3541
                class_=munge(ns_sym.name),
3542
                field=safe_name,
3543
                target=vars(ns_module)[safe_name],
3544
                env=ctx.get_node_env(pos=ctx.syntax_position),
3545
            )
3546

3547
        # Then allow builtins
3548
        safe_name = munge(form.name, allow_builtins=True)
1✔
3549
        if safe_name not in vars(ns_module):
1✔
3550
            raise ctx.AnalyzerException("can't identify aliased form", form=form)
1✔
3551

3552
        # Aliased imports generate code which uses the import alias, so we
3553
        # don't need to care if this is an import or an alias.
3554
        return MaybeHostForm(
1✔
3555
            form=form,
3556
            class_=munge(ns_sym.name),
3557
            field=safe_name,
3558
            target=vars(ns_module)[safe_name],
3559
            env=ctx.get_node_env(pos=ctx.syntax_position),
3560
        )
3561
    elif ns_sym in which_ns.aliases:
1✔
3562
        aliased_ns: runtime.Namespace = which_ns.aliases[ns_sym]
1✔
3563
        v = Var.find_in_ns(aliased_ns, sym.symbol(form.name))
1✔
3564
        if v is None:
1✔
3565
            raise ctx.AnalyzerException(
1✔
3566
                f"unable to resolve symbol '{sym.symbol(form.name, ns_sym.name)}' in this context",
3567
                form=form,
3568
            )
3569
        elif v.meta is not None and v.meta.val_at(SYM_PRIVATE_META_KEY, False):
1✔
3570
            raise ctx.AnalyzerException(
1✔
3571
                f"cannot resolve private Var {form.name} from namespace {form.ns}",
3572
                form=form,
3573
            )
3574
        return VarRef(
1✔
3575
            form=form,
3576
            var=v,
3577
            is_allow_var_indirection=_is_allow_var_indirection(form),
3578
            env=ctx.get_node_env(pos=ctx.syntax_position),
3579
        )
3580

3581
    return None
1✔
3582

3583

3584
def __resolve_namespaced_symbol(  # pylint: disable=too-many-branches  # noqa: MC0001
1✔
3585
    ctx: AnalyzerContext, form: sym.Symbol
3586
) -> Union[Const, HostField, MaybeClass, MaybeHostForm, VarRef]:
3587
    """Resolve a namespaced symbol into a Python name or Basilisp Var."""
3588
    assert form.ns is not None
1✔
3589

3590
    current_ns = ctx.current_ns
1✔
3591
    if form.ns == current_ns.name:
1✔
3592
        v = current_ns.find(sym.symbol(form.name))
1✔
3593
        if v is not None:
1✔
3594
            return VarRef(
1✔
3595
                form=form,
3596
                var=v,
3597
                is_allow_var_indirection=_is_allow_var_indirection(form),
3598
                env=ctx.get_node_env(pos=ctx.syntax_position),
3599
            )
3600
    elif form.ns == _BUILTINS_NS:
1✔
3601
        class_ = munge(form.name, allow_builtins=True)
1✔
3602
        target = getattr(builtins, class_, None)
1✔
3603
        if target is None:
1✔
3604
            raise ctx.AnalyzerException(
1✔
3605
                f"cannot resolve builtin function '{class_}'", form=form
3606
            )
3607
        return MaybeClass(
1✔
3608
            form=form,
3609
            class_=class_,
3610
            target=target,
3611
            env=ctx.get_node_env(pos=ctx.syntax_position),
3612
        )
3613

3614
    v = Var.find(form)
1✔
3615
    if v is not None:
1✔
3616
        # Disallow global references to Vars defined with :private metadata
3617
        if v.meta is not None and v.meta.val_at(SYM_PRIVATE_META_KEY, False):
1✔
3618
            raise ctx.AnalyzerException(
1✔
3619
                f"cannot resolve private Var {form.name} from namespace {form.ns}",
3620
                form=form,
3621
            )
3622
        return VarRef(
1✔
3623
            form=form,
3624
            var=v,
3625
            is_allow_var_indirection=_is_allow_var_indirection(form),
3626
            env=ctx.get_node_env(pos=ctx.syntax_position),
3627
        )
3628

3629
    if "." in form.name and form.name != _DOUBLE_DOT_MACRO_NAME:
1✔
3630
        raise ctx.AnalyzerException(
1✔
3631
            "symbol names may not contain the '.' operator", form=form
3632
        )
3633

3634
    resolved = __resolve_namespaced_symbol_in_ns(ctx, current_ns, form)
1✔
3635
    if resolved is not None:
1✔
3636
        return resolved
1✔
3637

3638
    if "." in form.ns:
1✔
3639
        try:
1✔
3640
            return _resolve_nested_symbol(ctx, form)
1✔
3641
        except CompilerException:
1✔
3642
            raise ctx.AnalyzerException(
1✔
3643
                f"unable to resolve symbol '{form}' in this context", form=form
3644
            ) from None
3645
    elif ctx.should_allow_unresolved_symbols:
1✔
3646
        return _const_node(form, ctx)
1✔
3647

3648
    # Imports and requires nested in function definitions, method definitions, and
3649
    # `(do ...)` forms are not statically resolvable, since they haven't necessarily
3650
    # been imported and we want to minimize side-effecting from the compiler. In these
3651
    # cases, we merely verify that we've seen the symbol before and defer to runtime
3652
    # checks by the Python VM to verify that the import or require is legitimate.
3653
    maybe_import_or_require_sym = sym.symbol(form.ns)
1✔
3654
    maybe_import_or_require_entry = ctx.symbol_table.find_symbol(
1✔
3655
        maybe_import_or_require_sym
3656
    )
3657
    if maybe_import_or_require_entry is not None:
1✔
3658
        if maybe_import_or_require_entry.context == LocalType.IMPORT:
1✔
3659
            ctx.symbol_table.mark_used(maybe_import_or_require_sym)
1✔
3660
            return MaybeHostForm(
1✔
3661
                form=form,
3662
                class_=munge(form.ns),
3663
                field=munge(form.name),
3664
                target=None,
3665
                env=ctx.get_node_env(pos=ctx.syntax_position),
3666
            )
3667

3668
    # Static and class methods on types in the current namespace can be referred
3669
    # to as `Type/static-method`. In these cases, we will try to resolve the
3670
    # namespace portion of the symbol as a Var within the current namespace.
3671
    maybe_type_or_class = current_ns.find(sym.symbol(form.ns))
1✔
3672
    if maybe_type_or_class is not None:
1✔
3673
        safe_name = munge(form.name)
1✔
3674
        member = getattr(maybe_type_or_class.value, safe_name, None)
1✔
3675

3676
        if member is None:
1✔
3677
            raise ctx.AnalyzerException(
1✔
3678
                f"unable to resolve static or class member '{form}' in this context",
3679
                form=form,
3680
            )
3681

3682
        return HostField(
1✔
3683
            form=form,
3684
            field=safe_name,
3685
            target=VarRef(
3686
                form=form,
3687
                var=maybe_type_or_class,
3688
                is_allow_var_indirection=_is_allow_var_indirection(form),
3689
                env=ctx.get_node_env(pos=ctx.syntax_position),
3690
            ),
3691
            is_assignable=False,
3692
            env=ctx.get_node_env(pos=ctx.syntax_position),
3693
        )
3694

3695
    raise ctx.AnalyzerException(
1✔
3696
        f"unable to resolve symbol '{form}' in this context", form=form
3697
    )
3698

3699

3700
def __resolve_bare_symbol(
1✔
3701
    ctx: AnalyzerContext, form: sym.Symbol
3702
) -> Union[Const, MaybeClass, VarRef]:
3703
    """Resolve a non-namespaced symbol into a Python name or a local Basilisp Var."""
3704
    assert form.ns is None
1✔
3705

3706
    # Look up the symbol in the namespace mapping of the current namespace.
3707
    current_ns = ctx.current_ns
1✔
3708
    v = current_ns.find(form)
1✔
3709
    if v is not None:
1✔
3710
        return VarRef(
1✔
3711
            form=form,
3712
            var=v,
3713
            is_allow_var_indirection=_is_allow_var_indirection(form),
3714
            env=ctx.get_node_env(pos=ctx.syntax_position),
3715
        )
3716

3717
    if "." in form.name:
1✔
3718
        raise ctx.AnalyzerException(
1✔
3719
            "symbol names may not contain the '.' operator", form=form
3720
        )
3721

3722
    munged = munge(form.name, allow_builtins=True)
1✔
3723
    if munged in vars(builtins):
1✔
3724
        return MaybeClass(
1✔
3725
            form=form,
3726
            class_=munged,
3727
            target=vars(builtins)[munged],
3728
            env=ctx.get_node_env(pos=ctx.syntax_position),
3729
        )
3730

3731
    # Allow users to resolve imported module names directly
3732
    maybe_import = current_ns.get_import(form)
1✔
3733
    if maybe_import is not None:
1✔
3734
        return MaybeClass(
1✔
3735
            form=form,
3736
            class_=munge(form.name),
3737
            target=maybe_import,
3738
            env=ctx.get_node_env(pos=ctx.syntax_position),
3739
        )
3740

3741
    if ctx.should_allow_unresolved_symbols:
1✔
3742
        return _const_node(form, ctx)
1✔
3743

3744
    assert munged not in vars(current_ns.module)
1✔
3745
    raise ctx.AnalyzerException(
1✔
3746
        f"unable to resolve symbol '{form}' in this context", form=form
3747
    )
3748

3749

3750
def _resolve_sym(
1✔
3751
    ctx: AnalyzerContext, form: sym.Symbol
3752
) -> Union[Const, HostField, MaybeClass, MaybeHostForm, VarRef]:
3753
    """Resolve a Basilisp symbol as a Var or Python name."""
3754
    # Support special class-name syntax to instantiate new classes
3755
    #   (Classname. *args)
3756
    #   (aliased.Classname. *args)
3757
    #   (fully.qualified.Classname. *args)
3758
    if (
1✔
3759
        form.ns is None
3760
        and form.name.endswith(".")
3761
        and form.name != _DOUBLE_DOT_MACRO_NAME
3762
    ):
3763
        try:
1✔
3764
            ns, name = form.name[:-1].rsplit(".", maxsplit=1)
1✔
3765
            form = sym.symbol(name, ns=ns)
1✔
3766
        except ValueError:
1✔
3767
            form = sym.symbol(form.name[:-1])
1✔
3768

3769
    if form.ns is not None:
1✔
3770
        return __resolve_namespaced_symbol(ctx, form)
1✔
3771
    else:
3772
        return __resolve_bare_symbol(ctx, form)
1✔
3773

3774

3775
@_analyze_form.register(sym.Symbol)
1✔
3776
@_with_loc
1✔
3777
def _symbol_node(
1✔
3778
    form: sym.Symbol, ctx: AnalyzerContext
3779
) -> Union[Const, HostField, Local, MaybeClass, MaybeHostForm, VarRef]:
3780
    if ctx.is_quoted:
1✔
3781
        return _const_node(form, ctx)
1✔
3782

3783
    sym_entry = ctx.symbol_table.find_symbol(form)
1✔
3784
    if sym_entry is not None:
1✔
3785
        ctx.symbol_table.mark_used(form)
1✔
3786
        return Local(
1✔
3787
            form=form,
3788
            name=form.name,
3789
            local=sym_entry.context,
3790
            is_assignable=sym_entry.binding.is_assignable,
3791
            env=ctx.get_node_env(pos=ctx.syntax_position),
3792
        )
3793

3794
    return _resolve_sym(ctx, form)
1✔
3795

3796

3797
@_analyze_form.register(dict)
1✔
3798
@_with_loc
1✔
3799
def _py_dict_node(form: dict, ctx: AnalyzerContext) -> Union[Const, PyDict]:
1✔
3800
    if ctx.is_quoted:
1✔
3801
        return _const_node(form, ctx)
1✔
3802

3803
    keys, vals = [], []
1✔
3804
    for k, v in form.items():
1✔
3805
        keys.append(_analyze_form(k, ctx))
1✔
3806
        vals.append(_analyze_form(v, ctx))
1✔
3807

3808
    return PyDict(
1✔
3809
        form=form,
3810
        keys=vec.vector(keys),
3811
        vals=vec.vector(vals),
3812
        env=ctx.get_node_env(pos=ctx.syntax_position),
3813
    )
3814

3815

3816
@_analyze_form.register(list)
1✔
3817
@_with_loc
1✔
3818
def _py_list_node(form: list, ctx: AnalyzerContext) -> Union[Const, PyList]:
1✔
3819
    if ctx.is_quoted:
1✔
3820
        return _const_node(form, ctx)
1✔
3821
    return PyList(
1✔
3822
        form=form,
3823
        items=vec.vector(map(lambda form: _analyze_form(form, ctx), form)),
3824
        env=ctx.get_node_env(pos=ctx.syntax_position),
3825
    )
3826

3827

3828
@_analyze_form.register(set)
1✔
3829
@_with_loc
1✔
3830
def _py_set_node(form: set, ctx: AnalyzerContext) -> Union[Const, PySet]:
1✔
3831
    if ctx.is_quoted:
1✔
3832
        return _const_node(form, ctx)
1✔
3833
    return PySet(
1✔
3834
        form=form,
3835
        items=vec.vector(map(lambda form: _analyze_form(form, ctx), form)),
3836
        env=ctx.get_node_env(pos=ctx.syntax_position),
3837
    )
3838

3839

3840
@_analyze_form.register(tuple)
1✔
3841
@_with_loc
1✔
3842
def _py_tuple_node(form: tuple, ctx: AnalyzerContext) -> Union[Const, PyTuple]:
1✔
3843
    if ctx.is_quoted:
1✔
3844
        return _const_node(form, ctx)
1✔
3845
    return PyTuple(
1✔
3846
        form=form,
3847
        items=vec.vector(map(lambda form: _analyze_form(form, ctx), form)),
3848
        env=ctx.get_node_env(pos=ctx.syntax_position),
3849
    )
3850

3851

3852
@_with_meta
1✔
3853
def _map_node(form: lmap.PersistentMap, ctx: AnalyzerContext) -> MapNode:
1✔
3854
    keys, vals = [], []
1✔
3855
    for k, v in form.items():
1✔
3856
        keys.append(_analyze_form(k, ctx))
1✔
3857
        vals.append(_analyze_form(v, ctx))
1✔
3858

3859
    return MapNode(
1✔
3860
        form=form,
3861
        keys=vec.vector(keys),
3862
        vals=vec.vector(vals),
3863
        env=ctx.get_node_env(pos=ctx.syntax_position),
3864
    )
3865

3866

3867
@_analyze_form.register(lmap.PersistentMap)
1✔
3868
@_with_loc
1✔
3869
def _map_node_or_quoted(
1✔
3870
    form: lmap.PersistentMap, ctx: AnalyzerContext
3871
) -> Union[Const, MapNode]:
3872
    if ctx.is_quoted:
1✔
3873
        return _const_node(form, ctx)
1✔
3874
    return _map_node(form, ctx)
1✔
3875

3876

3877
@_with_meta
1✔
3878
def _queue_node(form: lqueue.PersistentQueue, ctx: AnalyzerContext) -> QueueNode:
1✔
3879
    return QueueNode(
1✔
3880
        form=form,
3881
        items=vec.vector(map(lambda form: _analyze_form(form, ctx), form)),
3882
        env=ctx.get_node_env(pos=ctx.syntax_position),
3883
    )
3884

3885

3886
@_analyze_form.register(lqueue.PersistentQueue)
1✔
3887
@_with_loc
1✔
3888
def _queue_node_or_quoted(
1✔
3889
    form: lqueue.PersistentQueue, ctx: AnalyzerContext
3890
) -> Union[Const, QueueNode]:
3891
    if ctx.is_quoted:
1✔
3892
        return _const_node(form, ctx)
1✔
3893
    return _queue_node(form, ctx)
1✔
3894

3895

3896
@_with_meta
1✔
3897
def _set_node(form: lset.PersistentSet, ctx: AnalyzerContext) -> SetNode:
1✔
3898
    return SetNode(
1✔
3899
        form=form,
3900
        items=vec.vector(map(lambda form: _analyze_form(form, ctx), form)),
3901
        env=ctx.get_node_env(pos=ctx.syntax_position),
3902
    )
3903

3904

3905
@_analyze_form.register(lset.PersistentSet)
1✔
3906
@_with_loc
1✔
3907
def _set_node_or_quoted(
1✔
3908
    form: lset.PersistentSet, ctx: AnalyzerContext
3909
) -> Union[Const, SetNode]:
3910
    if ctx.is_quoted:
1✔
3911
        return _const_node(form, ctx)
1✔
3912
    return _set_node(form, ctx)
1✔
3913

3914

3915
@_with_meta
1✔
3916
def _vector_node(form: vec.PersistentVector, ctx: AnalyzerContext) -> VectorNode:
1✔
3917
    return VectorNode(
1✔
3918
        form=form,
3919
        items=vec.vector(map(lambda form: _analyze_form(form, ctx), form)),
3920
        env=ctx.get_node_env(pos=ctx.syntax_position),
3921
    )
3922

3923

3924
@_analyze_form.register(vec.PersistentVector)
1✔
3925
@_with_loc
1✔
3926
def _vector_node_or_quoted(
1✔
3927
    form: vec.PersistentVector, ctx: AnalyzerContext
3928
) -> Union[Const, VectorNode]:
3929
    if ctx.is_quoted:
1✔
3930
        return _const_node(form, ctx)
1✔
3931
    return _vector_node(form, ctx)
1✔
3932

3933

3934
@functools.singledispatch
1✔
3935
def _const_node_type(_: Any) -> ConstType:
1✔
UNCOV
3936
    return ConstType.UNKNOWN
×
3937

3938

3939
for tp, const_type in {
1✔
3940
    bool: ConstType.BOOL,
3941
    bytes: ConstType.BYTES,
3942
    complex: ConstType.NUMBER,
3943
    datetime: ConstType.INST,
3944
    Decimal: ConstType.DECIMAL,
3945
    dict: ConstType.PY_DICT,
3946
    float: ConstType.NUMBER,
3947
    Fraction: ConstType.FRACTION,
3948
    int: ConstType.NUMBER,
3949
    kw.Keyword: ConstType.KEYWORD,
3950
    list: ConstType.PY_LIST,
3951
    llist.PersistentList: ConstType.SEQ,
3952
    lmap.PersistentMap: ConstType.MAP,
3953
    lqueue.PersistentQueue: ConstType.QUEUE,
3954
    lset.PersistentSet: ConstType.SET,
3955
    IRecord: ConstType.RECORD,
3956
    ISeq: ConstType.SEQ,
3957
    IType: ConstType.TYPE,
3958
    type(re.compile("")): ConstType.REGEX,
3959
    set: ConstType.PY_SET,
3960
    sym.Symbol: ConstType.SYMBOL,
3961
    str: ConstType.STRING,
3962
    tuple: ConstType.PY_TUPLE,
3963
    type(None): ConstType.NIL,
3964
    uuid.UUID: ConstType.UUID,
3965
    vec.PersistentVector: ConstType.VECTOR,
3966
}.items():
3967
    _const_node_type.register(tp, lambda _, default=const_type: default)
1✔
3968

3969

3970
@_analyze_form.register(bool)
1✔
3971
@_analyze_form.register(bytes)
1✔
3972
@_analyze_form.register(complex)
1✔
3973
@_analyze_form.register(datetime)
1✔
3974
@_analyze_form.register(Decimal)
1✔
3975
@_analyze_form.register(float)
1✔
3976
@_analyze_form.register(Fraction)
1✔
3977
@_analyze_form.register(int)
1✔
3978
@_analyze_form.register(IRecord)
1✔
3979
@_analyze_form.register(IType)
1✔
3980
@_analyze_form.register(kw.Keyword)
1✔
3981
@_analyze_form.register(type(re.compile(r"")))
1✔
3982
@_analyze_form.register(str)
1✔
3983
@_analyze_form.register(type(None))
1✔
3984
@_analyze_form.register(uuid.UUID)
1✔
3985
@_with_loc
1✔
3986
def _const_node(form: ReaderForm, ctx: AnalyzerContext) -> Const:
1✔
3987
    assert (
1✔
3988
        (
3989
            ctx.is_quoted
3990
            and isinstance(
3991
                form,
3992
                (
3993
                    sym.Symbol,
3994
                    vec.PersistentVector,
3995
                    llist.PersistentList,
3996
                    lmap.PersistentMap,
3997
                    lqueue.PersistentQueue,
3998
                    lset.PersistentSet,
3999
                    ISeq,
4000
                ),
4001
            )
4002
        )
4003
        or (ctx.should_allow_unresolved_symbols and isinstance(form, sym.Symbol))
4004
        or (isinstance(form, (llist.PersistentList, ISeq)) and form.is_empty)
4005
        or isinstance(
4006
            form,
4007
            (
4008
                bool,
4009
                bytes,
4010
                complex,
4011
                datetime,
4012
                Decimal,
4013
                dict,
4014
                float,
4015
                Fraction,
4016
                int,
4017
                IRecord,
4018
                IType,
4019
                kw.Keyword,
4020
                list,
4021
                Pattern,
4022
                set,
4023
                str,
4024
                tuple,
4025
                type(None),
4026
                uuid.UUID,
4027
            ),
4028
        )
4029
    ), "Constant nodes must be composed of constant values"
4030

4031
    node_type = _const_node_type(form)
1✔
4032
    assert node_type != ConstType.UNKNOWN, "Only allow known constant types"
1✔
4033

4034
    descriptor = Const(
1✔
4035
        form=form,
4036
        is_literal=True,
4037
        type=node_type,
4038
        val=form,
4039
        env=ctx.get_node_env(pos=ctx.syntax_position),
4040
    )
4041

4042
    if hasattr(form, "meta"):
1✔
4043
        form_meta = _clean_meta(form.meta)  # type: ignore
1✔
4044
        if form_meta is not None:
1✔
4045
            meta_ast = _const_node(form_meta, ctx)
1✔
4046
            assert isinstance(meta_ast, MapNode) or (
1✔
4047
                isinstance(meta_ast, Const) and meta_ast.type == ConstType.MAP
4048
            )
4049
            return descriptor.assoc(meta=meta_ast, children=vec.v(META))
1✔
4050

4051
    return descriptor
1✔
4052

4053

4054
###################
4055
# Public Functions
4056
###################
4057

4058

4059
def analyze_form(ctx: AnalyzerContext, form: ReaderForm) -> Node:
1✔
4060
    """Take a Lisp form as an argument and produce a Basilisp syntax
4061
    tree matching the clojure.tools.analyzer AST spec."""
4062
    return _analyze_form(form, ctx).assoc(top_level=True)
1✔
4063

4064

4065
def macroexpand_1(form: ReaderForm) -> ReaderForm:
1✔
4066
    """Macroexpand form one time. Returns the macroexpanded form. The return
4067
    value may still represent a macro. Does not macroexpand child forms."""
4068
    ctx = AnalyzerContext(
1✔
4069
        "<Macroexpand>", should_macroexpand=False, allow_unresolved_symbols=True
4070
    )
4071
    maybe_macro = analyze_form(ctx, form)
1✔
4072
    if maybe_macro.op == NodeOp.INVOKE:
1✔
4073
        assert isinstance(maybe_macro, Invoke)
1✔
4074

4075
        fn = maybe_macro.fn
1✔
4076
        if fn.op == NodeOp.VAR and isinstance(fn, VarRef):
1✔
4077
            if _is_macro(fn.var):
1✔
4078
                assert isinstance(form, ISeq)
1✔
4079
                macro_env = ctx.symbol_table.as_env_map()
1✔
4080
                return fn.var.value(macro_env, form, *form.rest)
1✔
4081
    return maybe_macro.form
1✔
4082

4083

4084
def macroexpand(form: ReaderForm) -> ReaderForm:
1✔
4085
    """Repeatedly macroexpand form as by macroexpand-1 until form no longer
4086
    represents a macro. Returns the expanded form. Does not macroexpand child
4087
    forms."""
4088
    return analyze_form(
1✔
4089
        AnalyzerContext("<Macroexpand>", allow_unresolved_symbols=True), form
4090
    ).form
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc