Skip to content

Commit db6026b

Browse files
committed
Add support for returning expression types to lisp
The bulk of this commit is slightly dull code for printing information about libclang cursors in sexp form.
1 parent 74cfe8b commit db6026b

File tree

11 files changed

+1004
-1
lines changed

11 files changed

+1004
-1
lines changed

irony-exprtype.el

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
;;; irony-exprtype.el --- Type information at point. -*- lexical-binding: t -*-
2+
;;; Commentary:
3+
;;
4+
;; See Irony::exprtype in Irony.cpp.
5+
;;
6+
;;; Code:
7+
8+
(require 'irony)
9+
(require 'pcase)
10+
11+
(defun irony-exprtype (&optional callback)
12+
"Return type information string at point.
13+
14+
If region is active, will use the region instead."
15+
(interactive)
16+
(if (use-region-p)
17+
(irony-exprtype-region (region-beginning) (region-end) callback)
18+
(irony-exprtype-region (point) (point) callback)))
19+
20+
(defun irony-exprtype-region (start end callback)
21+
(irony--send-file-request
22+
"exprtype"
23+
;; FIXME Why "list" of a callback?
24+
(list (apply-partially #'irony-exprtype--handler callback))
25+
(number-to-string (1- (position-bytes start)))
26+
(number-to-string (1- (position-bytes end)))))
27+
28+
(defun irony-exprtype--fix-offsets (thing)
29+
;; Adjust all values of the form (bound byte-offset byte-offset)
30+
;; to use char offsets instead.
31+
(when (consp thing)
32+
(pcase (car thing)
33+
(`(bounds . (,start-bytes . (,end-bytes . nil)))
34+
(let ((start (byte-to-position (1+ start-bytes)))
35+
(end (byte-to-position (1+ end-bytes))))
36+
(setcar thing (list 'bounds start end))))
37+
(_ (irony-exprtype--fix-offsets (car thing))))
38+
(irony-exprtype--fix-offsets (cdr thing))))
39+
40+
(defun irony-exprtype--handler (callback exprtype)
41+
(irony-exprtype--fix-offsets exprtype)
42+
(if callback (funcall callback exprtype) (message "%S" exprtype)))
43+
44+
;; ;; FIXME Remove when no longer helpful
45+
;; (trace-function #'irony-exprtype)
46+
;; (trace-function #'irony-exprtype-region)
47+
;; (trace-function #'irony-exprtype--handler)
48+
;; (trace-function #'process-send-string)
49+
;; (trace-function #'start-process)
50+
;; (trace-function #'irony--send-file-request)
51+
;; (trace-function #'irony--server-process-filter)
52+
;; (trace-function #'irony--start-server-process)
53+
54+
(provide 'irony-exprtype)
55+
;; Local Variables:
56+
;; byte-compile-warnings: (not cl-functions)
57+
;; End:
58+
;;; irony-exprtype.el ends here

server/src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ add_executable(irony-server
2323
support/NonCopyable.h
2424
support/TemporaryFile.cpp
2525
support/TemporaryFile.h
26+
support/Sexp.cpp
2627

2728
Command.cpp
2829
Commands.def

server/src/Command.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/**
22
* \file
33
* \author Guillaume Papin <[email protected]>
4-
*
4+
*
55
* \brief Command parser definitions.
66
*
77
* This file is distributed under the GNU General Public License. See
@@ -158,6 +158,13 @@ Command *CommandParser::parse(const std::vector<std::string> &argv) {
158158
handleUnsaved = true;
159159
break;
160160

161+
case Command::ExprType:
162+
positionalArgs.push_back(StringConverter(&command_.file));
163+
positionalArgs.push_back(UnsignedIntConverter(&command_.line));
164+
positionalArgs.push_back(UnsignedIntConverter(&command_.column));
165+
handleUnsaved = true;
166+
break;
167+
161168
case Command::Help:
162169
case Command::Exit:
163170
break;

server/src/Commands.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ X(Diagnostics, "diagnostics", "FILE - look for diagnostics in file")
1616
X(Complete,
1717
"complete",
1818
"FILE LINE COL - perform code completion at a given location")
19+
X(ExprType,
20+
"exprtype",
21+
"FILE START END - get type of expression between byte offsets START and END")
1922
X(Help, "help", "show this message")
2023
X(Exit, "exit", "exit interactive mode, print nothing")
2124
X(SetDebug, "set-debug", "[on|off] - enable or disable verbose logging")

server/src/Irony.cpp

Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
#include "Irony.h"
1414

1515
#include "support/iomanip_quoted.h"
16+
#include "support/Sexp.h"
17+
using sexp::repr;
1618

1719
#include <algorithm>
1820
#include <array>
@@ -342,3 +344,289 @@ void Irony::complete(const std::string &file,
342344
std::cout << ")\n";
343345
}
344346
}
347+
348+
unsigned getOffset(CXSourceLocation loc) {
349+
unsigned offset, line, col;
350+
clang_getSpellingLocation(loc, nullptr, &line, &col, &offset);
351+
return offset;
352+
}
353+
354+
unsigned getOffset(CXCursor cursor) {
355+
CXSourceLocation sloc = clang_getCursorLocation(cursor);
356+
return getOffset(sloc);
357+
}
358+
359+
CXCursor cursorParent(CXCursor cursor) {
360+
CXCursor parent = clang_getCursorLexicalParent(cursor);
361+
if (clang_isInvalid(clang_getCursorKind(parent))) {
362+
parent = clang_getCursorSemanticParent(cursor);
363+
}
364+
return parent;
365+
}
366+
367+
/// Whether cursor is suitable for having its information being shown
368+
/// to the user.
369+
bool isCursorPrintable(CXCursor cursor) {
370+
CXType type = clang_getCursorType(cursor);
371+
bool valid = !clang_isInvalid(cursor.kind) &&
372+
!clang_isUnexposed(cursor.kind) &&
373+
!clang_isTranslationUnit(cursor.kind);
374+
// Unexposed types are allowed (common in C++)
375+
bool validType = type.kind != CXType_Invalid;
376+
bool isLiteral = cursor.kind == CXCursor_IntegerLiteral ||
377+
cursor.kind == CXCursor_FloatingLiteral ||
378+
cursor.kind == CXCursor_ImaginaryLiteral ||
379+
cursor.kind == CXCursor_StringLiteral ||
380+
cursor.kind == CXCursor_CharacterLiteral;
381+
return valid && validType && !isLiteral;
382+
}
383+
384+
CXCursor getToplevelCursor(CXTranslationUnit tu, CXSourceLocation sloc) {
385+
CXCursor cursor = clang_getCursor(tu, sloc);
386+
CXCursorKind kind = clang_getCursorKind(cursor);
387+
while (!clang_isTranslationUnit(kind) && !clang_isInvalid(kind)) {
388+
CXCursor parent = clang_getCursorLexicalParent(cursor);
389+
if (clang_isInvalid(clang_getCursorKind(parent))) {
390+
parent = clang_getCursorSemanticParent(cursor);
391+
}
392+
if (clang_isTranslationUnit(clang_getCursorKind(parent))) {
393+
break;
394+
}
395+
cursor = parent;
396+
kind = clang_getCursorKind(cursor);
397+
}
398+
return cursor;
399+
}
400+
401+
CXCursor getCursorFirstChild(CXCursor cursor) {
402+
CXCursor first_child = clang_getNullCursor();
403+
auto visitor = [](CXCursor current, CXCursor, CXClientData client_data)
404+
-> CXChildVisitResult {
405+
*static_cast<CXCursor *>(client_data) = current;
406+
return CXChildVisit_Break;
407+
};
408+
clang_visitChildren(cursor, visitor, &first_child);
409+
return first_child;
410+
}
411+
412+
/// Wrapper around clang_getCursorExtent
413+
struct Range {
414+
Range(CXCursor cursor)
415+
: range(clang_getCursorExtent(cursor))
416+
, start(clang_getRangeStart(range))
417+
, end(clang_getRangeEnd(range))
418+
, start_offset(getOffset(start))
419+
, end_offset(getOffset(end)) {
420+
}
421+
bool contains(unsigned offset) {
422+
return start_offset <= offset && offset < end_offset;
423+
}
424+
bool contains(unsigned a, unsigned b) {
425+
return start_offset <= std::min(a, b) && std::max(a, b) <= end_offset;
426+
}
427+
CXSourceRange range;
428+
CXSourceLocation start, end;
429+
unsigned start_offset, end_offset;
430+
};
431+
432+
struct exprtypeAlist {
433+
exprtypeAlist(const CXCursor &cursor) : cursor(cursor) {
434+
}
435+
const CXCursor &cursor;
436+
};
437+
438+
struct typeAlist {
439+
typeAlist(const CXType &type) : type(type) {
440+
}
441+
const CXType &type;
442+
};
443+
444+
std::ostream &operator<<(std::ostream &out, const typeAlist &proxy) {
445+
CXType type = proxy.type;
446+
if (type.kind == CXType_Invalid)
447+
return out << "nil";
448+
out << "(";
449+
if (type.kind != CXType_Unexposed)
450+
out << sexp::alistEntry("kind", repr(type.kind));
451+
out << sexp::alistEntry("spelling", repr(clang_getTypeSpelling(type)));
452+
453+
int numargs = clang_getNumArgTypes(type);
454+
if (numargs >= 0) {
455+
out << " (args";
456+
for (int i = 0; i < numargs; ++i) {
457+
CXType arg = clang_getArgType(type, i);
458+
out << sexp::alistEntry("type", typeAlist(arg));
459+
}
460+
out << ")";
461+
}
462+
463+
if (clang_isFunctionTypeVariadic(type)) {
464+
out << " (variadic . t)";
465+
}
466+
467+
return out << ")";
468+
}
469+
470+
std::ostream& operator<<(std::ostream& out, const exprtypeAlist &proxy) {
471+
CXCursor cursor = proxy.cursor;
472+
473+
/* Examples of what might be printed (from test.cc)
474+
475+
FIXME More robust examples and testing
476+
477+
(exprtype (bounds 832 836) (kind . CallExpr) (type (kind . Dependent) (spelling . "<dependent type>")) (call (bounds 832 833) (kind . OverloadedDeclRef) (spelling . "h") (overloaded (completion (comment . "docstring for h(X).") (priority . 50) (chunks (ResultType . "X") "h(" (Placeholder . "const X &x")")")) (completion (comment . "docstring for h<T>") (priority . 50) (chunks (ResultType . "T") "h(" (Placeholder . "const T &x")")")))) (args (834 . 835)))
478+
479+
(exprtype (bounds 834 835) (kind . DeclRefExpr) (spelling . "t") (type (spelling . "T")) (completion (priority . 50) (chunks (ResultType . "T") "t")))
480+
481+
(exprtype (bounds 892 902) (kind . ParmDecl) (spelling . "y") (type (kind . LValueReference) (spelling . "const Y &")) (completion (priority . 50) (chunks (ResultType . "const Y &") "y")))
482+
483+
(exprtype (bounds 1287 1291) (kind . CallExpr) (spelling . "f") (type (kind . Void) (spelling . "void")) (completion (comment . "docstring for f") (priority . 50) (chunks (ResultType . "void") "f(" (Placeholder . "string x") ")")) (call (bounds 1287 1288) (kind . DeclRefExpr) (spelling . "f") (type (kind . FunctionProto) (spelling . "void (string)") (args (type (kind . Typedef) (spelling . "string")))) (completion (comment . "docstring for f") (priority . 50) (chunks (ResultType . "void") "f(" (Placeholder . "string x") ")")) (comment . "docstring for f")) (args (1289 . 1290)) (comment . "docstring for f"))
484+
485+
(exprtype (bounds 1289 1290) (kind . DeclRefExpr) (spelling . "x") (type (kind . Typedef) (spelling . "string")) (completion (priority . 50) (chunks (ResultType . "string") "x")))
486+
487+
NOTE: no arguments here despite this being a CallExpr
488+
(exprtype (bounds 1205 1227) (kind . CallExpr) (spelling . "vector") (type (spelling . "vector<float>")) (completion (parent . "std::__1::vector") (priority . 50) (chunks "vector(" (Placeholder . "size_type __n") "," (Placeholder . "const_reference __x") ")")) (call (bounds 1205 1211) (kind . TemplateRef) (spelling . "vector") (completion (parent . "std::__1") (priority . 50) (chunks "vector<" (Placeholder . "class _Tp") (Optional . (completion (priority . 0) (chunks "," (Placeholder . "class _Allocator")))) ">"))))
489+
490+
*/
491+
492+
Range range{cursor};
493+
CXType type = clang_getCursorType(cursor);
494+
495+
// This is a long alist of essentially arbitrary elements.
496+
if (!clang_isInvalid(cursor.kind))
497+
out << " (bounds " << range.start_offset << " " << range.end_offset << ")";
498+
out << sexp::alistEntry("kind", repr(cursor.kind))
499+
<< sexp::alistEntry("spelling", repr(clang_getCursorSpelling(cursor)))
500+
<< sexp::alistEntry("type", typeAlist(type));
501+
502+
// Find a suitable completion string
503+
{
504+
CXCompletionString completion =
505+
clang_getCursorCompletionString(clang_getCursorDefinition(cursor));
506+
if (!completion)
507+
completion =
508+
clang_getCursorCompletionString(clang_getCursorReferenced(cursor));
509+
if (!completion)
510+
completion = clang_getCursorCompletionString(cursor);
511+
if (completion)
512+
out << " " << sexp::completion(completion);
513+
}
514+
515+
// If cursor is a call to a function, we show the type of the
516+
// function as well. Then the type is the return type.
517+
if (cursor.kind == CXCursor_CallExpr) {
518+
// Find the first child, which should be the function
519+
// FIXME Is this true for obj-c? I'm not familiar with it.
520+
CXCursor funref = cursor;
521+
for (CXCursor cursor_child = funref; !clang_isInvalid(cursor_child.kind);
522+
funref = cursor_child, cursor_child = getCursorFirstChild(funref))
523+
;
524+
525+
out << " (call" << exprtypeAlist(funref) << ")";
526+
}
527+
528+
// FIXME Constructor arguments don't seem to appear here.
529+
// Are they of kind CallExpr?
530+
int numargs = clang_Cursor_getNumArguments(cursor);
531+
if (numargs >= 0) {
532+
out << " (args";
533+
for (int i = 0; i < numargs; ++i) {
534+
CXCursor arg = clang_Cursor_getArgument(cursor, i);
535+
Range range{arg};
536+
out << " (" << range.start_offset << " . " << range.end_offset << ")";
537+
}
538+
out << ")";
539+
}
540+
541+
out << sexp::alistEntry("comment", sexp::comment(cursor));
542+
543+
// Overloaded decl ref
544+
{
545+
CXCursor ref = cursor;
546+
int num_overloaded = clang_getNumOverloadedDecls(ref);
547+
if (!num_overloaded) {
548+
ref = clang_getCursorReferenced(cursor);
549+
num_overloaded = clang_getNumOverloadedDecls(ref);
550+
}
551+
if (num_overloaded > 0) {
552+
out << " (overloaded";
553+
for (int i = 0; i < num_overloaded; ++i) {
554+
CXCursor decl = clang_getOverloadedDecl(ref, i);
555+
out << " " << sexp::completion(decl);
556+
}
557+
out << ")";
558+
}
559+
}
560+
561+
return out;
562+
}
563+
564+
void Irony::exprtype(const std::string &file,
565+
unsigned start_offset,
566+
unsigned end_offset,
567+
const std::vector<std::string> &flags,
568+
const std::vector<CXUnsavedFile> &unsavedFiles) {
569+
// NOTE Duplicate from complete(..) above
570+
TUManager::Settings settings;
571+
settings.parseTUOptions |= CXTranslationUnit_CacheCompletionResults;
572+
#if HAS_BRIEF_COMMENTS_IN_COMPLETION
573+
settings.parseTUOptions |=
574+
CXTranslationUnit_IncludeBriefCommentsInCodeCompletion;
575+
#endif
576+
(void)tuManager_.registerSettings(settings);
577+
CXTranslationUnit tu = tuManager_.getOrCreateTU(file, flags, unsavedFiles);
578+
579+
if (!tu) {
580+
std::cout << "nil\n";
581+
return;
582+
}
583+
584+
// if (true || debug_) {
585+
// std::cout << "+++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n";
586+
// dumpDiagnostics(tu);
587+
// std::cout << "+++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n";
588+
// }
589+
590+
if (end_offset < start_offset)
591+
end_offset = start_offset;
592+
593+
CXFile cxfile = clang_getFile(tu, file.c_str());
594+
CXSourceLocation
595+
start_loc = clang_getLocationForOffset(tu, cxfile, start_offset);
596+
597+
CXCursor cursor = clang_getCursor(tu, start_loc);
598+
599+
/* The lexical parent of a cursor doesn't seem to be its parent in
600+
the AST, which is what we want. So go to the toplevel cursor, just
601+
below the translation unit, and walk down looking for printable
602+
cursors covering the region. */
603+
if (!isCursorPrintable(cursor) ||
604+
!Range(cursor).contains(start_offset, end_offset)) {
605+
cursor = getToplevelCursor(tu, start_loc);
606+
struct data_t {
607+
const unsigned start_offset, end_offset;
608+
CXCursor cursor;
609+
} data{start_offset, end_offset, clang_getNullCursor()};
610+
auto visitor = [](CXCursor current, CXCursor, CXClientData client_data)
611+
-> CXChildVisitResult {
612+
Range range{current};
613+
data_t &data = *static_cast<data_t *>(client_data);
614+
if (range.contains(data.start_offset, data.end_offset)) {
615+
if (isCursorPrintable(current))
616+
data.cursor = current;
617+
return CXChildVisit_Recurse;
618+
}
619+
return range.start_offset >= data.end_offset ? CXChildVisit_Break
620+
: CXChildVisit_Continue;
621+
};
622+
clang_visitChildren(cursor, visitor, &data);
623+
if (!clang_Cursor_isNull(data.cursor))
624+
cursor = data.cursor;
625+
}
626+
627+
std::cout << "(exprtype";
628+
unsigned numdiag = clang_getNumDiagnostics(tu);
629+
if (numdiag)
630+
std::cout << sexp::alistEntry("diagnostics", numdiag);
631+
std::cout << exprtypeAlist(cursor) << ")" << std::endl;
632+
}

0 commit comments

Comments
 (0)