feat(lsp): leave the extension verdicts to the registry
Diagnostics no longer repeat the generated table's ignorance of a registered mnemonic: unknown-instruction and unencodable-instruction against a statement the registry encodes are filtered from the server's own presentation, driven by asm.LookupExtension directly. The filter is a no-op once lint learns the registry, so the two compose unchanged. Assisted-by: GLM 5.3 Flash
This commit is contained in:
1 parent
dadeda144a
commit
2d803e38d8
3 files changed
+116
No files matched your search
@@ -21,6 +21,8 @@ import (
|
||||
|
||||
"sourcedock.dev/petrbalvin/gasm-sdk/arch"
|
||||
"sourcedock.dev/petrbalvin/gasm-sdk/asm"
|
||||
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
|
||||
"sourcedock.dev/petrbalvin/gasm-sdk/lint"
|
||||
)
|
||||
|
||||
// extFormDoc is the rendering of one registered form: everything hover and
|
||||
@@ -346,3 +348,39 @@ func applyExtensionCompletion(items []CompletionItem, a arch.Arch) []CompletionI
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
// extOwnedCode reports whether a lint code carries a verdict the extension
|
||||
// layer owns: the two that accuse a mnemonic of not existing or of not being
|
||||
// encodable. The operand-count rule stays with the generated table, whose
|
||||
// bounds the relaxed architectures never fire against a registered form.
|
||||
func extOwnedCode(code string) bool {
|
||||
return code == lint.CodeUnknownInstr || code == lint.CodeUnencodable
|
||||
}
|
||||
|
||||
// extensionMnemonicSites collects the source positions of the statements
|
||||
// whose mnemonic the architecture's extension layer registers. The
|
||||
// diagnostics pass consults it to leave the layer's verdicts to the layer:
|
||||
// a registry mnemonic is neither unknown nor unencodable, the registry
|
||||
// encodes it.
|
||||
func extensionMnemonicSites(f *ast.File, a arch.Arch) map[[2]int]bool {
|
||||
out := make(map[[2]int]bool)
|
||||
if f == nil {
|
||||
return out
|
||||
}
|
||||
for _, d := range f.Decls {
|
||||
t, ok := d.(*ast.Text)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
for _, s := range t.Body {
|
||||
in, ok := s.(*ast.Instr)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if _, ok := asm.LookupExtension(a, in.Mnemonic.Text); ok {
|
||||
out[[2]int{in.Mnemonic.Pos.Line, in.Mnemonic.Pos.Column}] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -283,3 +283,71 @@ func TestExtensionHoverWithoutProse(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestDiagnosticsExtensionOwnedARM64 pins the pass-through on arm64: the
|
||||
// lint on this base still reports a registered mnemonic as unknown, and the
|
||||
// server filters its own presentation through the registry, while a mnemonic
|
||||
// no layer registers stays reported.
|
||||
func TestDiagnosticsExtensionOwnedARM64(t *testing.T) {
|
||||
diags := publishedDiagnostics(t, "file:///v_arm64.s", sveDoc)
|
||||
for _, d := range diags {
|
||||
if strings.Contains(d.Message, "SQADD") {
|
||||
t.Errorf("registry mnemonic still reported: %+v", d)
|
||||
}
|
||||
}
|
||||
unknown := 0
|
||||
for _, d := range diags {
|
||||
if d.Code == "unknown-instruction" {
|
||||
unknown++
|
||||
if !strings.Contains(d.Message, "NOSUCHTHING") {
|
||||
t.Errorf("unknown-instruction against the wrong statement: %+v", d)
|
||||
}
|
||||
}
|
||||
}
|
||||
if unknown != 1 {
|
||||
t.Errorf("unknown-instruction count = %d, want the one unregistered mnemonic", unknown)
|
||||
}
|
||||
}
|
||||
|
||||
// TestDiagnosticsExtensionOwnedFocused is the focused filter test: the
|
||||
// server's own diagnostic computation, without the protocol, answers nothing
|
||||
// against the registered mnemonics and keeps everything else.
|
||||
func TestDiagnosticsExtensionOwnedFocused(t *testing.T) {
|
||||
srv := &Server{docs: map[string]string{"file:///v_arm64.s": sveDoc}}
|
||||
diags := srv.diagnosticsFor("file:///v_arm64.s")
|
||||
if len(diags) == 0 {
|
||||
t.Fatal("diagnostics empty, want the unregistered mnemonic")
|
||||
}
|
||||
for _, d := range diags {
|
||||
switch {
|
||||
case strings.Contains(d.Message, "SQADD"):
|
||||
t.Errorf("registry mnemonic still reported: %+v", d)
|
||||
case d.Code == "unknown-instruction":
|
||||
if !strings.Contains(d.Message, "NOSUCHTHING") {
|
||||
t.Errorf("unexpected unknown-instruction: %+v", d)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestDiagnosticsExtensionAMD64StillReported is the per-architecture
|
||||
// negative case: on amd64 the registry owns nothing, so the same source
|
||||
// keeps every unknown-instruction report, the SVE mnemonics included.
|
||||
func TestDiagnosticsExtensionAMD64StillReported(t *testing.T) {
|
||||
diags := publishedDiagnostics(t, "file:///v_amd64.s", sveDoc)
|
||||
found := map[string]bool{}
|
||||
for _, d := range diags {
|
||||
if d.Code == "unknown-instruction" {
|
||||
for _, mnem := range []string{"SQADD", "NOSUCHTHING"} {
|
||||
if strings.Contains(d.Message, mnem) {
|
||||
found[mnem] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, mnem := range []string{"SQADD", "NOSUCHTHING"} {
|
||||
if !found[mnem] {
|
||||
t.Errorf("amd64 lost the unknown-instruction report for %q: %+v", mnem, diags)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1127,6 +1127,13 @@ func (s *Server) diagnosticsFor(uri string) []Diagnostic {
|
||||
cfg := lint.Config{Arch: arch.FromFilename(uriPath(uri))}
|
||||
diags := lint.File(f, cfg)
|
||||
|
||||
// The extension layer owns the verdict on its own mnemonics: the registry
|
||||
// encodes them, so a report of unknown or unencodable against a
|
||||
// registered mnemonic is the generated table's ignorance, not the file's
|
||||
// defect. The filter consults the registry directly and is a no-op once
|
||||
// lint learns the registry itself, so the two compose unchanged.
|
||||
owned := extensionMnemonicSites(f, cfg.Arch)
|
||||
|
||||
out := make([]Diagnostic, 0, len(diags)+len(errs))
|
||||
for _, e := range errs {
|
||||
pos := token.Position{Line: 1, Column: 1}
|
||||
@@ -1142,6 +1149,9 @@ func (s *Server) diagnosticsFor(uri string) []Diagnostic {
|
||||
})
|
||||
}
|
||||
for _, d := range diags {
|
||||
if extOwnedCode(d.Code) && owned[[2]int{d.Pos.Line, d.Pos.Column}] {
|
||||
continue
|
||||
}
|
||||
out = append(out, Diagnostic{
|
||||
Range: clientRange(text, toRange(d.Pos.Line, d.Pos.Column, d.End)),
|
||||
Severity: lintSeverity(d.Severity),
|
||||
|
||||
Reference in new issue
Block a user