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/arch"
|
||||||
"sourcedock.dev/petrbalvin/gasm-sdk/asm"
|
"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
|
// 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
|
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))}
|
cfg := lint.Config{Arch: arch.FromFilename(uriPath(uri))}
|
||||||
diags := lint.File(f, cfg)
|
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))
|
out := make([]Diagnostic, 0, len(diags)+len(errs))
|
||||||
for _, e := range errs {
|
for _, e := range errs {
|
||||||
pos := token.Position{Line: 1, Column: 1}
|
pos := token.Position{Line: 1, Column: 1}
|
||||||
@@ -1142,6 +1149,9 @@ func (s *Server) diagnosticsFor(uri string) []Diagnostic {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
for _, d := range diags {
|
for _, d := range diags {
|
||||||
|
if extOwnedCode(d.Code) && owned[[2]int{d.Pos.Line, d.Pos.Column}] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
out = append(out, Diagnostic{
|
out = append(out, Diagnostic{
|
||||||
Range: clientRange(text, toRange(d.Pos.Line, d.Pos.Column, d.End)),
|
Range: clientRange(text, toRange(d.Pos.Line, d.Pos.Column, d.End)),
|
||||||
Severity: lintSeverity(d.Severity),
|
Severity: lintSeverity(d.Severity),
|
||||||
|
|||||||
Reference in new issue
Block a user