James and all,
Just another idea: make the “don’t use nonlocal return inside handlers” rule visible while you type.
A linter could indicate the error with a wavy underline under the ^ the moment you write it inside a guarded block.
How it would work:
- open a
.scfile - Type:
{}.try { ^42 }
You should see a colorful wavy underline under the ^ (Flymake warning: ^ inside try block)
The parser can’t reliably forbid ^ there, but we can lint the typical case: a block immediately following one of those selectors that contains a nonlocal return.
;;; sclang-return-guard-simple.el --- Proof-of-concept/simple linter -*- lexical-binding: t; -*-
;; usage:
;; (require 'sclang-return-guard-simple)
(require 'flymake)
(defgroup sclang-return-guard nil
"Lint for nonlocal returns."
:group 'languages)
(defcustom sclang-return-guard-selectors '("try" "protect" "use")
"Selectors to check."
:type '(repeat string)
:group 'sclang-return-guard)
(defun sclang-return-guard-check-buffer ()
"Check buffer for problematic returns."
(let ((diagnostics '()))
(save-excursion
(goto-char (point-min))
(while (re-search-forward "\\(try\\|protect\\|use\\)[[:space:]]*{" nil t)
(let ((method-name (match-string 1))
(block-start (1- (point))))
(goto-char block-start)
(when (eq (char-after) ?{)
(condition-case nil
(progn
(forward-sexp) ; Jump to matching }
(let ((block-end (point)))
(goto-char (1+ block-start))
(while (search-forward "^" block-end t)
(let ((caret-pos (1- (point))))
(unless (save-excursion
(goto-char caret-pos)
(or (nth 3 (syntax-ppss)) ; in string
(nth 4 (syntax-ppss)))) ; in comment
(push (flymake-make-diagnostic
(current-buffer)
caret-pos
(1+ caret-pos)
:warning
(format "^ inside %s block" method-name))
diagnostics))))))
(error nil))))))
diagnostics))
(defun sclang-return-guard-backend (report-fn &rest _args)
"Flymake backend."
(funcall report-fn (sclang-return-guard-check-buffer)))
;;;###autoload
(define-minor-mode sclang-return-guard-mode
"Simple return guard."
:lighter " ^guard"
(if sclang-return-guard-mode
(progn
(add-hook 'flymake-diagnostic-functions
#'sclang-return-guard-backend nil t)
(when (fboundp 'flymake-mode)
(flymake-mode 1)))
(remove-hook 'flymake-diagnostic-functions
#'sclang-return-guard-backend t)))
;;;###autoload
(defun sclang-return-guard-auto-enable ()
"Auto-enable for SuperCollider files."
(when (and (stringp buffer-file-name)
(string-match "\\.\\(scd\\|sc\\)\\'" buffer-file-name))
(sclang-return-guard-mode 1)))
(add-hook 'find-file-hook #'sclang-return-guard-auto-enable)
(provide 'sclang-return-guard-simple)
;;; sclang-return-guard-simple.el ends here
Future
-
“Code action” (“convert to return-at-method-level”): turn
try { ^compute() } { |e| ^fallback }into
var ret; try { ret = compute() } { |e| ret = fallback }; ^ret
NOTE: A tree‑sitter would do the same, but safely and precisely. For our return‑inside‑handler issue, it turns a weak text search into a precise AST rule—and that’s the difference between “works in my demo” and “always works while you type.”
