feat(lsp): flag direct dot access on nullable values
Attribute reads now check the base symbol detail (`T?` annotation) and emit the same error as the compiler when `?.` is missing. `?.` and pattern-bound non-null captures stay clean.
This commit is contained in:
1 parent
1911f70818
commit
2781ba7466
1 file changed
+53
-1
@@ -989,6 +989,48 @@ def _CheckAssignValue(pool: memhub.MemManager*, doc: LspDocuments.LspDocument*,
|
||||
_EmitTypeError(pool, doc, value, msg, errors)
|
||||
|
||||
|
||||
def _NullableDetail(det: str) -> int:
|
||||
"""1 when an annotation text is the nullable `T?` form."""
|
||||
if det is None: return 0
|
||||
if string.strchr(det, '?') is not None: return 1
|
||||
return 0
|
||||
|
||||
|
||||
def _SymDetailAt(pool: memhub.MemManager*, a: LspSymbols.LspAnalysis*,
|
||||
expr: ast.AST*) -> str:
|
||||
"""Resolved symbol detail (annotation text) at a Name/Attribute expression."""
|
||||
if a is None or expr is None: return None
|
||||
line0: int = 0
|
||||
col: int = 0
|
||||
name: str = None
|
||||
if expr.kind() == ast.ASTKind.Name:
|
||||
nm: ast.Name* = (ast.Name*)(expr)
|
||||
if nm.id is None: return None
|
||||
line0 = nm.lineno - 1
|
||||
col = nm.col_offset
|
||||
name = nm.id
|
||||
elif expr.kind() == ast.ASTKind.Attribute:
|
||||
at: ast.Attribute* = (ast.Attribute*)(expr)
|
||||
if at.attr is None: return None
|
||||
line0 = at.lineno - 1
|
||||
col = at.col_offset
|
||||
name = at.attr
|
||||
else:
|
||||
return None
|
||||
colu: int = col
|
||||
if a.doc is not None: colu = LspSource.ByteColToUtf16(a.doc, line0, col)
|
||||
if a.occs is not None:
|
||||
i: int = 0
|
||||
while i < int(len(a.occs)):
|
||||
o: LspSymbols.LspOccurrence* = a.occs.get(sizet(i))
|
||||
i += 1
|
||||
if o is not None and o.line == line0 and o.sc == colu and o.name is not None:
|
||||
if string.strcmp(o.name, name) == 0:
|
||||
if o.sym is not None: return o.sym.detail
|
||||
return None
|
||||
return None
|
||||
|
||||
|
||||
def _WalkChecksExprs(pool: memhub.MemManager*, doc: LspDocuments.LspDocument*,
|
||||
a: LspSymbols.LspAnalysis*, ws: LspWorkspace.LspWorkspace*,
|
||||
table_pool: memhub.MemManager*, e: ast.AST*,
|
||||
@@ -1041,7 +1083,17 @@ def _WalkChecksExprs(pool: memhub.MemManager*, doc: LspDocuments.LspDocument*,
|
||||
i += 1
|
||||
return
|
||||
if k == ast.ASTKind.Attribute:
|
||||
_WalkChecksExprs(pool, doc, a, ws, table_pool, ast.Attribute*(e).value, errors)
|
||||
at_c: ast.Attribute* = ast.Attribute*(e)
|
||||
if at_c.optional == 0 and at_c.attr is not None:
|
||||
det_b: str = _SymDetailAt(pool, a, at_c.value)
|
||||
if _NullableDetail(det_b) != 0:
|
||||
msg_b: bytes = pool.alloc(sizet(320))
|
||||
if msg_b is not None:
|
||||
stdio.snprintf(msg_b, sizet(320),
|
||||
"cannot use '.%s' on a nullable value: it may be None; use `?.` or check it against None first",
|
||||
at_c.attr)
|
||||
_EmitTypeError(pool, doc, e, str(msg_b), errors)
|
||||
_WalkChecksExprs(pool, doc, a, ws, table_pool, at_c.value, errors)
|
||||
return
|
||||
if k == ast.ASTKind.Subscript:
|
||||
sb: ast.Subscript* = ast.Subscript*(e)
|
||||
|
||||
Reference in new issue
Block a user