Skip to content

Commit 092bc5f

Browse files
committed
feat(analysis): move logic for checking keyword args closer to where types are checked
1 parent 39b4916 commit 092bc5f

File tree

2 files changed

+99
-88
lines changed

2 files changed

+99
-88
lines changed

elsa-analyser.el

Lines changed: 75 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -897,7 +897,7 @@ If no type annotation is provided, find the value type through
897897
t)))
898898
(t spec)))
899899

900-
(defun elsa--check-argument-for-index (index argument-form overloads state all-overloads overloads-errors)
900+
(defun elsa--check-argument-for-index (index min max argument-form overloads state all-overloads overloads-errors)
901901
(let ((good-overloads nil)
902902
(new-overloads nil))
903903
(when (elsa-type-is-empty-p (elsa-get-type argument-form))
@@ -912,36 +912,62 @@ If no type annotation is provided, find the value type through
912912
;; error.
913913
(setq
914914
good-overloads
915-
(-filter
915+
(-keep
916916
(-lambda ((overload . overload-index))
917-
(let* ((expected (elsa-function-type-nth-arg overload index))
918-
(expected-normalized (or expected (elsa-type-mixed)))
919-
(actual (oref argument-form type))
920-
(acceptablep (elsa-type-could-assign-p expected-normalized actual)))
921-
(cond
922-
((not expected)
923-
(push (list overload overload-index
924-
(format
925-
"Argument %s is present but the function signature does not define it. Missing overload?"
926-
(if (numberp index) (1+ index) index)))
927-
overloads-errors))
928-
((trinary-false-p acceptablep)
929-
(push (list overload overload-index
930-
;; we need to sanitize the % sign in the error
931-
;; because it is later passed to format in
932-
;; `elsa-make-error'. And the "but received"
933-
;; string can contain arbitrary text as one
934-
;; possible type is (const "whatever")
935-
(elsa-with-temp-explainer explainer
936-
(elsa-explain-and-indent explainer
937-
("Argument %s accepts type `%s' but received `%s'"
938-
(if (numberp index) (1+ index) index)
939-
(elsa-type-describe expected-normalized)
940-
(elsa-type-describe actual))
941-
(elsa-type-accept expected-normalized actual explainer))
942-
explainer))
943-
overloads-errors)))
944-
(and expected (trinary-possible-p acceptablep))))
917+
(let* ((last-type-is-keys (elsa-type-keys-p (-last-item (oref overload args))))
918+
(index (cond
919+
((< index min) index)
920+
((and (symbolp max) last-type-is-keys)
921+
;; this is most likely a keyword argument,
922+
;; so we check if it's a keyword and if so
923+
;; pass it forward
924+
(cond
925+
;; if this index is a keyword, we just
926+
;; skip to the next argument form.
927+
((and (elsa-form-keyword-p argument-form)
928+
(cl-evenp (- index min)))
929+
nil)
930+
;; if the previous form was a keyword,
931+
;; that's the thing we need to look up
932+
((elsa-form-keyword-p
933+
(oref argument-form previous))
934+
(elsa-form-to-lisp
935+
(oref argument-form previous)))
936+
(t index)))
937+
(t index))))
938+
(if (not index)
939+
(list overload overload-index index)
940+
(let* ((expected (elsa-function-type-nth-arg overload index))
941+
(expected-normalized (or expected (elsa-type-mixed)))
942+
(actual (oref argument-form type))
943+
(acceptablep (elsa-type-could-assign-p expected-normalized actual)))
944+
(cond
945+
((not expected)
946+
(push (list overload overload-index
947+
(if (numberp index) argument-form (oref argument-form previous))
948+
(format
949+
"Argument %s is present but the function signature does not define it. Missing overload?"
950+
(if (numberp index) (1+ index) index)))
951+
overloads-errors))
952+
((trinary-false-p acceptablep)
953+
(push (list overload overload-index
954+
(if (numberp index) argument-form (oref argument-form previous))
955+
;; we need to sanitize the % sign in the error
956+
;; because it is later passed to format in
957+
;; `elsa-make-error'. And the "but received"
958+
;; string can contain arbitrary text as one
959+
;; possible type is (const "whatever")
960+
(elsa-with-temp-explainer explainer
961+
(elsa-explain-and-indent explainer
962+
("Argument %s accepts type `%s' but received `%s'"
963+
(if (numberp index) (1+ index) index)
964+
(elsa-type-describe expected-normalized)
965+
(elsa-type-describe actual))
966+
(elsa-type-accept expected-normalized actual explainer))
967+
explainer))
968+
overloads-errors)))
969+
(when (and expected (trinary-possible-p acceptablep))
970+
(list overload overload-index index))))))
945971
overloads))
946972
(if good-overloads
947973
;; If we have multiple overloads where the argument is of a
@@ -952,30 +978,31 @@ If no type annotation is provided, find the value type through
952978
;; Because the `good-overloads' are only those which accept
953979
;; the arguments, we can sort them by `elsa-type-accept' and
954980
;; pick the last (smallest) one.
955-
(if (= (length good-overloads) 1)
956-
(setq new-overloads good-overloads)
957-
(setq new-overloads (elsa--simplify-overloads good-overloads index)))
981+
(mapcar
982+
(-lambda ((overload overload-index))
983+
(cons overload overload-index))
984+
(if (= (length good-overloads) 1)
985+
(setq new-overloads good-overloads)
986+
(setq new-overloads (elsa--simplify-overloads good-overloads))))
958987
(setq new-overloads nil)
959988
(elsa-state-add-message state
960989
(if (< 1 (length overloads-errors))
961990
(let ((explainer (elsa-explainer)))
962991
(elsa-explain-and-indent explainer
963992
("No overload matches this call")
964993
(-each (-sort (-on #'< #'cadr) overloads-errors)
965-
(-lambda ((overload overload-index o-expl))
994+
(-lambda ((overload overload-index _ o-expl))
966995
(elsa-explain-and-indent explainer
967996
("Overload %d of %d: '%s'"
968997
(1+ overload-index)
969998
(length all-overloads)
970999
(elsa-tostring overload))
9711000
(elsa--append-explainer explainer o-expl)))))
972-
(elsa-make-error argument-form
1001+
(elsa-make-error (nth 2 (car overloads-errors))
9731002
explainer
9741003
:code "no-overloads"))
975-
(elsa-make-error (if (keywordp index)
976-
(oref argument-form previous)
977-
argument-form)
978-
(let ((expl-or-fmt (nth 2 (car overloads-errors))))
1004+
(elsa-make-error (nth 2 (car overloads-errors))
1005+
(let ((expl-or-fmt (nth 3 (car overloads-errors))))
9791006
(if (elsa-explainer-p expl-or-fmt)
9801007
(elsa--reset-depth expl-or-fmt)
9811008
expl-or-fmt))
@@ -1055,38 +1082,15 @@ SCOPE and STATE are the scope and state objects."
10551082
(overloads (--map-indexed (cons it it-index) all-overloads)))
10561083
(-each-indexed args
10571084
(lambda (index argument-form)
1058-
(when-let ((arg-idx (cond
1059-
((< index min) index)
1060-
((symbolp max)
1061-
;; this is most likely a
1062-
;; keyword argument, so we
1063-
;; check if it's a keyword
1064-
;; and if so pass it forward
1065-
(cond
1066-
;; if this index is a
1067-
;; keyword, we just skip to
1068-
;; the next argument form.
1069-
((and (elsa-form-keyword-p argument-form)
1070-
(cl-evenp (- index min)))
1071-
nil)
1072-
;; if the previous form was
1073-
;; a keyword, that's the
1074-
;; thing we need to look up
1075-
((elsa-form-keyword-p
1076-
(oref argument-form previous))
1077-
(elsa-form-to-lisp
1078-
(oref argument-form previous)))
1079-
(t index)))
1080-
(t index))))
1081-
(let ((check-results
1082-
(elsa--check-argument-for-index
1083-
arg-idx
1084-
argument-form overloads state
1085-
all-overloads overloads-errors)))
1086-
(setq overloads-errors
1087-
(append overloads-errors (plist-get check-results :errors)))
1088-
(setq overloads (plist-get check-results :overloads))
1089-
(unless overloads (throw 'no-overloads nil))))))
1085+
(let ((check-results
1086+
(elsa--check-argument-for-index
1087+
index min max
1088+
argument-form overloads state
1089+
all-overloads overloads-errors)))
1090+
(setq overloads-errors
1091+
(append overloads-errors (plist-get check-results :errors)))
1092+
(setq overloads (plist-get check-results :overloads))
1093+
(unless overloads (throw 'no-overloads nil)))))
10901094
(mapcar #'car overloads))))))
10911095
;; set the return type of the form according to the return type
10921096
;; of the function's declaration

elsa-type-helpers.el

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -329,28 +329,35 @@ might not make sense."
329329
(push (list item) groups))))
330330
groups))
331331

332-
(defun elsa--simplify-overloads (list index)
332+
(defun elsa--simplify-overloads (list)
333333
"Select most specific overloads from LIST.
334334
335335
If overloads are not comparable, select all of them.
336336
337+
Items in LIST are of form (TYPE OVERLOAD-INDEX INDEX).
338+
337339
INDEX is the position in the arglist or a keyword for named
338-
arguments.
339-
340-
Items in LIST are of form (TYPE . OVERLOAD-INDEX)."
341-
(-mapcat
342-
#'car
343-
(elsa--poset-sort
344-
(lambda (a b)
345-
(elsa-type-accept
346-
(elsa-function-type-nth-arg (caar b) index)
347-
(elsa-function-type-nth-arg (caar a) index)))
348-
(elsa--equivalence-classes
349-
(lambda (a b)
350-
(elsa-type-equivalent-p
351-
(elsa-function-type-nth-arg (car b) index)
352-
(elsa-function-type-nth-arg (car a) index)))
353-
list))))
340+
arguments."
341+
(let ((parts (--separate (nth 2 it) list)))
342+
(append
343+
(cadr parts)
344+
(-mapcat
345+
#'car
346+
(elsa--poset-sort
347+
;; we order the equivalence classes by the first element of the
348+
;; group
349+
(lambda (a b)
350+
(let ((first (car a))
351+
(second (car b)))
352+
(elsa-type-accept
353+
(elsa-function-type-nth-arg (car second) (nth 2 second))
354+
(elsa-function-type-nth-arg (car first) (nth 2 first)))))
355+
(elsa--equivalence-classes
356+
(lambda (a b)
357+
(elsa-type-equivalent-p
358+
(elsa-function-type-nth-arg (car b) (nth 2 b))
359+
(elsa-function-type-nth-arg (car a) (nth 2 a))))
360+
(car parts)))))))
354361

355362
(defun elsa-type-is-empty-p (this)
356363
"Test if THIS is type-equivalent to `elsa-type-empty'.

0 commit comments

Comments
 (0)