Skip to content

Commit a9f4221

Browse files
committed
Better handle diagnostics pointing outside of the workspace.
Errors in macros now are marked as "external" so that there is a hint that the file isn't in your project. It is also marked read-only when you open it.
1 parent caf7cbd commit a9f4221

File tree

4 files changed

+98
-58
lines changed

4 files changed

+98
-58
lines changed

rust/messages.py

Lines changed: 82 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,14 @@ def _click_handler(view, url, hide_popup=False):
376376
if hide_popup:
377377
view.hide_popup()
378378
elif url.startswith('file:///'):
379-
view.window().open_file(url[8:], sublime.ENCODED_POSITION)
379+
path = url[8:]
380+
external = False
381+
if path.endswith(':external'):
382+
path = path[:-9]
383+
external = True
384+
new_view = view.window().open_file(path, sublime.ENCODED_POSITION)
385+
if external:
386+
new_view.set_read_only(True)
380387
elif url.startswith('replace:'):
381388
info = urllib.parse.parse_qs(url[8:], keep_blank_values=True)
382389
_accept_replace(view, info['id'][0], info['replacement'][0])
@@ -796,7 +803,7 @@ def add_rust_messages(window, base_path, info, target_path, msg_cb):
796803
:param target_path: Absolute path to the top-level source file of the
797804
target (lib.rs, main.rs, etc.). May be None if it is not known.
798805
:param msg_cb: Callback that will be given the message object (and each
799-
child separately).
806+
child separately). May be None.
800807
"""
801808
# cargo check emits in a slightly different format.
802809
if 'reason' in info:
@@ -815,7 +822,7 @@ def add_rust_messages(window, base_path, info, target_path, msg_cb):
815822
return
816823
if _is_duplicate_message(window, primary_message):
817824
return
818-
batches = _batch_and_cross_link(primary_message)
825+
batches = _batch_and_cross_link(window, primary_message)
819826
_save_batches(window, batches, msg_cb)
820827

821828

@@ -830,8 +837,15 @@ def _is_duplicate_message(window, primary_message):
830837
return False
831838

832839

833-
def _is_external_macro(span):
834-
return 'macros>' in span['file_name'] or span['file_name'].startswith('/rustc/')
840+
def _is_external(window, path):
841+
if 'macros>' in path:
842+
return True
843+
if not os.path.isabs(path):
844+
return False
845+
for folder in window.folders():
846+
if path.startswith(folder + os.sep):
847+
return False
848+
return True
835849

836850

837851
def _collect_rust_messages(window, base_path, info, target_path,
@@ -931,33 +945,31 @@ def set_primary_message(span, text):
931945
message.text = text
932946
message.level = level_from_str(info['level'])
933947

934-
def add_additional(span, text, level, suggested_replacement=None):
948+
def add_additional(window, span, text, level, suggested_replacement=None):
935949
child = Message()
936950
child.text = text
937951
child.suggested_replacement = suggested_replacement
938952
child.level = level_from_str(level)
939953
child.primary = False
940-
if _is_external_macro(span):
941-
# Nowhere to display this, just send it to the console via msg_cb.
942-
msg_cb(child)
943-
else:
944-
child.path = make_span_path(span)
945-
if not os.path.exists(child.path):
946-
# Sometimes rust gives messages that link to libstd in the
947-
# directory where it was built (such as on Travis).
948-
return
949-
child.span = make_span_region(span)
950-
if any(map(lambda m: m.is_similar(child), message.children)):
951-
# Duplicate message, skip. This happens with some of the
952-
# macro help messages.
953-
return
954-
child.parent = message
955-
message.children.append(child)
954+
child.path = make_span_path(span)
955+
if not os.path.exists(child.path):
956+
# Sometimes rust gives messages that link to libstd in the
957+
# directory where it was built (such as on CI).
958+
if msg_cb:
959+
msg_cb(child)
960+
return
961+
child.span = make_span_region(span)
962+
if any(map(lambda m: m.is_similar(child), message.children)):
963+
# Duplicate message, skip. This happens with some of the
964+
# macro help messages.
965+
return
966+
child.parent = message
967+
message.children.append(child)
956968

957969
if len(info['spans']) == 0:
958970
if parent_info:
959971
# This is extra info attached to the parent message.
960-
add_additional(parent_info['span'], info['message'], info['level'])
972+
add_additional(window, parent_info['span'], info['message'], info['level'])
961973
else:
962974
# Messages without spans are global session messages (like "main
963975
# function not found").
@@ -992,7 +1004,7 @@ def find_span_r(span, expansion=None):
9921004
return span, expansion
9931005

9941006
for span in info['spans']:
995-
if _is_external_macro(span):
1007+
if _is_external(window, span['file_name']):
9961008
# Rust gives the chain of expansions for the macro, which we don't
9971009
# really care about. We want to find the site where the macro was
9981010
# invoked. I'm not entirely confident this is the best way to do
@@ -1007,43 +1019,62 @@ def find_span_r(span, expansion=None):
10071019
updated['suggested_replacement'] = span['suggested_replacement']
10081020
span = updated
10091021

1010-
if _is_external_macro(span):
1011-
# Macros from extern crates do not have 'expansion', and thus
1012-
# we do not have a location to highlight. Place the result at
1013-
# the bottom of the primary target path.
1022+
if _is_external(window, span['file_name']):
10141023
macro_name = span['file_name']
1015-
if target_path:
1016-
span['file_name'] = target_path
1017-
span['line_start'] = None
1018-
# else, messages will be shown in console via msg_cb.
1019-
add_additional(span,
1020-
'Errors occurred in macro %s from external crate' % (macro_name,),
1024+
if not os.path.exists(span['file_name']):
1025+
# Macros from extern crates do not have 'expansion', and thus
1026+
# we do not have a location to highlight. Place the result at
1027+
# somewhere relevant.
1028+
if parent_info:
1029+
show_in_span = parent_info['span']
1030+
else:
1031+
for span in info['spans']:
1032+
if span['is_primary']:
1033+
show_in_span = span
1034+
break
1035+
else:
1036+
# This shouldn't happen.
1037+
show_in_span = None
1038+
1039+
if show_in_span:
1040+
span['file_name'] = show_in_span['file_name']
1041+
span['byte_start'] = show_in_span['byte_start']
1042+
span['byte_end'] = show_in_span['byte_end']
1043+
span['line_start'] = show_in_span['line_start']
1044+
span['line_end'] = show_in_span['line_end']
1045+
span['column_start'] = show_in_span['column_start']
1046+
span['column_end'] = show_in_span['column_end']
1047+
elif target_path:
1048+
span['file_name'] = target_path
1049+
span['line_start'] = None
1050+
# else, messages will be shown in console via msg_cb.
1051+
add_additional(window, span,
1052+
'Errors occurred in %s from external crate' % (macro_name,),
10211053
info['level'])
10221054
text = ''.join([x['text'] for x in span['text']])
1023-
print('macro text: `%s`' % (text,))
10241055
if text:
1025-
add_additional(span,
1056+
add_additional(window, span,
10261057
'Macro text: %s' % (text,),
10271058
info['level'])
10281059
else:
10291060
if not expansion or not expansion['def_site_span'] \
1030-
or _is_external_macro(expansion['def_site_span']):
1031-
add_additional(span,
1061+
or _is_external(window, expansion['def_site_span']['file_name']):
1062+
add_additional(window, span,
10321063
'this error originates in a macro outside of the current crate',
10331064
info['level'])
10341065

10351066
# Add a message for macro invocation site if available in the local
10361067
# crate.
10371068
if span['expansion'] and \
1038-
not _is_external_macro(span) and \
1069+
not _is_external(window, span['file_name']) and \
10391070
not span['expansion']['macro_decl_name'].startswith('#['):
10401071
invoke_span, expansion = find_span_r(span)
1041-
add_additional(invoke_span, 'in this macro invocation', 'help')
1072+
add_additional(window, invoke_span, 'in this macro invocation', 'help')
10421073

10431074
if span['is_primary']:
10441075
if parent_info:
10451076
# Primary child message.
1046-
add_additional(span, info['message'], info['level'])
1077+
add_additional(window, span, info['message'], info['level'])
10471078
else:
10481079
set_primary_message(span, info['message'])
10491080

@@ -1058,11 +1089,11 @@ def find_span_r(span, expansion=None):
10581089
# multiple spans (starting in 1.21).
10591090
if label is not None:
10601091
# Display the label for this Span.
1061-
add_additional(span, label, info['level'])
1092+
add_additional(window, span, label, info['level'])
10621093
if span['suggested_replacement'] is not None:
10631094
# The "suggested_replacement" contains the code that should
10641095
# replace the span.
1065-
add_additional(span, None, 'help',
1096+
add_additional(window, span, None, 'help',
10661097
suggested_replacement=span['suggested_replacement'])
10671098

10681099
# Recurse into children (which typically hold notes).
@@ -1072,19 +1103,24 @@ def find_span_r(span, expansion=None):
10721103
message)
10731104

10741105

1075-
def _batch_and_cross_link(primary_message):
1106+
def _batch_and_cross_link(window, primary_message):
10761107
"""Creates a list of MessageBatch objects with appropriate cross links."""
10771108
def make_file_path(msg):
1109+
if _is_external(window, msg.path):
1110+
external = ':external'
1111+
else:
1112+
external = ''
10781113
if msg.span:
1079-
return 'file:///%s:%s:%s' % (
1114+
return 'file:///%s:%s:%s%s' % (
10801115
msg.path.replace('\\', '/'),
10811116
msg.span[1][0] + 1,
10821117
msg.span[1][1] + 1,
1118+
external,
10831119
)
10841120
else:
10851121
# Arbitrarily large line number to force it to the bottom of the
10861122
# file, since we don't know ahead of time how large the file is.
1087-
return 'file:///%s:999999999' % (msg.path,)
1123+
return 'file:///%s:999999999%s' % (msg.path, external)
10881124

10891125
# Group messages by line.
10901126
primary_batch = PrimaryBatch(primary_message)

rust/themes.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ def render(self, view, batch, for_popup=False):
125125
if isinstance(batch, PrimaryBatch):
126126
for url, path in batch.child_links:
127127
msgs.append(self.LINK_TMPL.format(
128-
url=url, text='See Also:', path=path))
128+
url=url, text=see_also(url), path=path))
129129
else:
130130
if batch.back_link:
131131
msgs.append(self.LINK_TMPL.format(
@@ -274,7 +274,7 @@ def icon(level):
274274
for url, path in batch.child_links:
275275
links.append(
276276
self.LINK_TMPL.format(
277-
url=url, text='See Also:', path=path))
277+
url=url, text=see_also(url), path=path))
278278
text = batch.primary_message.escaped_text(view, ' ' + icon('none'))
279279
if not text and not children:
280280
return None
@@ -327,8 +327,8 @@ def add_fake(msg, text):
327327
messages.append(fake)
328328

329329
if isinstance(batch, PrimaryBatch):
330-
for link in batch.child_links:
331-
add_fake(batch.primary_message, 'See Also: ' + link[1])
330+
for url, path in batch.child_links:
331+
add_fake(batch.primary_message, see_also(url) + ' ' + path)
332332
else:
333333
if batch.back_link:
334334
add_fake(batch.first(), 'See Primary: ' + batch.back_link[1])
@@ -340,3 +340,10 @@ def add_fake(msg, text):
340340
'solid': SolidTheme(),
341341
'test': TestTheme(),
342342
}
343+
344+
def see_also(path):
345+
print(path)
346+
if path.endswith(':external'):
347+
return 'See Also (external):'
348+
else:
349+
return 'See Also:'

tests/error-tests/tests/E0005.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ fn main() {
1515
// ^^^^^^^ERR pattern `None` not covered
1616
// ^^^^^^^ERR refutable pattern in local binding
1717
// ^^^^^^^MSG(>=1.39.0-beta,<1.44.0-beta) See Also: ↑:1
18-
// ^^^^^^^MSG(>=1.44.0-beta) See Also: ↓
18+
// ^^^^^^^MSG(>=1.44.0-beta) See Also (external): option.rs:
1919
// ^^^^^^^NOTE(>=1.40.0-beta) `let` bindings require
2020
// ^^^^^^^NOTE(>=1.40.0-beta) for more information
2121
// ^^^^^^^NOTE(>=1.44.0-beta) the matched value
@@ -25,6 +25,3 @@ fn main() {
2525
// Bug: https://github.com/rust-lang/rust/issues/64769
2626
// start-msg: ERR(>=1.39.0-beta,<1.44.0-beta) not covered
2727
// start-msg: MSG(>=1.39.0-beta,<1.44.0-beta) See Primary: ↓:14
28-
// end-msg: ERR(>=1.44.0-beta) Errors occurred in macro
29-
// end-msg: ERR(>=1.44.0-beta) not covered
30-
// end-msg: MSG(>=1.44.0-beta) See Primary: ↑:14

tests/error-tests/tests/impl-generic-mismatch.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ impl Hash for X {
5959
// ^^^^^^^^^^^ERR method `hash` has incompatible signature
6060
// ^^^^^^^^^^^ERR(>=1.28.0-beta) expected generic parameter
6161
// ^^^^^^^^^^^ERR(<1.28.0-beta) annotation in impl
62-
// ^^^^^^^^^^^MSG(>=1.32.0) See Also: ↓
62+
// ^^^^^^^^^^^ERR(>=1.32.0,<1.44.0-beta) Errors occurred in
63+
// ^^^^^^^^^^^ERR(>=1.32.0,<1.44.0-beta) Macro text:
64+
// ^^^^^^^^^^^ERR(>=1.32.0,<1.44.0-beta) method `hash` has incompatible
65+
// ^^^^^^^^^^^MSG(>=1.44.0-beta) See Also (external): mod.rs:
6366
}
64-
// end-msg: ERR(>=1.32.0) Errors occurred in macro
65-
// end-msg: ERR(>=1.32.0) declaration in trait here
66-
// end-msg: MSG(>=1.32.0) See Primary: ↑:58

0 commit comments

Comments
 (0)