diff --git a/App/lib/lsp/LspChecks.vp b/App/lib/lsp/LspChecks.vp index 18a1a13..6ad9090 100644 --- a/App/lib/lsp/LspChecks.vp +++ b/App/lib/lsp/LspChecks.vp @@ -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)