Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Documenting Dafny Entities (Adding Docstring) #3756

Merged
merged 15 commits into from
Mar 22, 2023
Merged

Conversation

MikaelMayer
Copy link
Member

@MikaelMayer MikaelMayer commented Mar 17, 2023

We gathered as a team last July to discuss how to add first-class support for Docstring, which is the interpretation of a special comment used for documentation purposes, e.g. hovering a symbol and getting more information about it.

We asked the following questions:

  • What entities will have docstrings attached to them?. We decided that, for now: fields, methods (also lemmas), functions (also predicates), datatypes, datatype constructors, opaque types, subset types, newtypes, classes, traits and export declarations would have a docstring associated to them.
    We did not consider attaching docstring to function parameters yet, nor docstring to requires/ensures clause (but the {:error} attribute can help) so I leave these out of scope for now.

  • What comment syntax to use for docstring?. We considered //, /* .. */, /// and /** ... */ and concluded that, in places where a comment would never be present except if it were a docstring (see below), all comments would count as docstrings and be concatenated together. In places a comment could be present and might or might not be a docstring (e.g. //TODO...), we would only support /***/ kind of docstring.

  • From what comments would docstring be extracted?.
    Comments can be placed virtually between any two tokens in the code. There were essentially two ways to place comments so that they can be interpreted as doscstring: Either above the entity, or below the entity. It turns out that there was no consensus on which one would be preferred (50%-50% of voices for a quorum of 6). Placing the comment below looks more like a regular definition (here is the entity, here is the definition) and is taken by some languages (Python, Caml...), while placing it above is what some other languages like C# or Java do.
    Our collective decision was to interpret comments placed below automatically as docstring first, because they are not ambiguous, and start to use it by default when writing Dafny ourselves. If not below, we would recognize comments above /** ... */ as docstring.
    You will find below two same source code with the two different versions.

  • What syntax do we support for converting docstring?.
    By default, we only remove comment signs and properly re-indent the docstring, but we do not process it further.
    Because Dafny's currently supported IDE has VSCode whicih displays hover messages in Markdown, we expect users to write their docstring in Markdown though.
    We also decided that we would enable a plugin-based rewriting of the docstring so that users can format it the way they want, and that Javadoc could be an example of such a plugin that we would provide as an example but not provide support for it, as users should relatively easily be able to override it.

Implementation

  • This PR adds a new interface IHasDocstring with the method .GetDocstring(DafnyOptions options)
    All nodes implement the method but only a specific subset implement the interface. The docstrings are tested with respect to our decision in DafnyPipeline
  • The first "*" and space of every long comment is removed, except if there was a line after the first one which did not start with a star. If there is no star at all, multiline comments are left-trimmed to match the space before the /* + two spaces, assuming the /* is the first non-whitespace character of the line.
  • This PR also adds a small Javadoc-like-to-markdown converter that can be activated with the flag --javadoclike-docstring-plugin, which I hope will be activated by default when using the language server from a future version of VSCode. The goal is to provide a helper for those who are used to this syntax, not support the entire javadoc language
  • This PR also adds support for the hover handler to actually display docstring when hovering over existing symbols. I left out of scope to add support for more symbol lookup as I saw some were missing.

Screenshots

image

Additional notes

I can split this PR on demand if necessary.

Appendix

Docstring after/below entities - preferred choice (example)

/// file comments


module A
  // module comment
{
}


method read(x: string, y: string) returns (z: int)
  // reads file in this.buffer
  modifies this`buffer
  ensures z > 0
  ensures z == abs(x + y)
{ … }

var x := 7
// This is docstring about x

// This is just a comment and not a docstring
const C := 8 // This is docstring about C

const C := 8
// This is docstring about C
// On two lines

datatype T0 =  // Docstring for T
  | A(x: int,
      y: int) // Docstring for A
  | B()       /* Docstring for B */ |
    C()       // Docstring for C

datatype T1 =
  // Docstring for T
  | A(x: int,
      y: int)
    // Docstring for A
  /* Still a docstring for A */ | B() // This is for B
  | C() /* Docstring for C */


export
  // This is the eponymous export set intended for most clients
  provides A, B, C


export Friends extends M
  // This export set is for clients who need to know more of the
  // details of the module's definitions.
  reveals A
  provides D

Docstring before/above entities, also supported (example)

/// Not a docstring, can be used as the file comment or for literate programming

/** module comment */
module A {
}


/** reads file in this.buffer */
method read(x: string, y: string) returns (z: string)
  modifies this`buffer
  ensures z > 0
  ensures z == abs(x + y)
{ … }

/** This is docstring about x */
var x := 7
// This is docstring about x part 2


/** This is docstring about x */
var x := 7


/** Docstring for T0*/
datatype T0 =
  | /** Docstring for A */
    A(x: int,
      y: int)
  | /** Docstring for B */
    B()
  | /** Docstring for C */
    C()


/** This is the eponymous export set intended for most clients */
export provides A, B, C

/** This export set is for clients who need to know more of the
  * details of the module's definitions. */
export Friends extends M
  reveals A
  provides D

By submitting this pull request, I confirm that my contribution is made under the terms of the MIT license.

return EndToken.TrailingTrivia;
}

return GetTriviaContainingDocstringFromStartTokeOrNull();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return GetTriviaContainingDocstringFromStartTokeOrNull();
return GetTriviaContainingDocstringFromStartTokenOrNull();

And anywhere else this name is used

var match = matches[i];
if (match.Groups["multiline"].Success) {
// For each line except the first,
// we need to remove the indentation up to the first "/" of the comment
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand 'up to the first /'
I would think that if there is initial white space followed by a * and optional space but not a /, that text is removed.
There is initial white space and then one or more * and a / then that is a comment end.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rephrased to

// we need to remove the indentation on every line.
// The length of removed indentation is maximum the space before the first "/*" + 3 characters


method test(d: D, t: T, e: Even) {
// ^[Rich comment for D]
// ^[D whose value is even] // Not working yet
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not working yet? A later PR?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. It's another mechanism with symbols that needs to be fixed, maybe @keyboardDrummer who implemented jump to definition can figure out why there is no information on hovering the subset type T and Even

function Test10(i: int): int
/* Test10 computes an int
It takes an int and adds 10 to it */
{ i + 1 }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
{ i + 1 }
{ i + 10 }

function Test11(i: int): int
/** Test11 computes an int
* It takes an int and adds 11 to it */
{ i + 1 }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
{ i + 1 }
{ i + 11 }

public void DocstringWorksForSubsetType() {
DocstringWorksFor(@"
type Odd = x: int | x % 2 == 1 witness 1
// Type of number that are not divisible by 2
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Type of number that are not divisible by 2
// Type of numbers that are not divisible by 2

// we need to remove the indentation up to the first "/" of the comment
// Additionally, if there is a "* " or a " *" or a " * ", we remove it as well
// provided it always started with a star.
//var startIndex = match.Groups["multiline"].Index;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should these next two lines be deleted?

@@ -21,6 +23,11 @@ public interface ICanFormat : INode {
bool SetIndent(int indentBefore, TokenNewIndentCollector formatter);
}

// A node that can have a docstring attached to it
public interface IHasDocstring : INode {
string GetDocstring(DafnyOptions options);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also have a method call GetDocstringSummary that produces a summary (e.g. the first sentence) of the docstring. Certainly a dafny doc tool would need such, though it could be defined in that tool if you don't think other tools would need it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To extract the first sentence, you need to do something non-trivial, such as detecting a period in the docstring, which seems arbitrary. We haven't discussed that the docstring should have one sentence first that indicates the summary (for example a good docstring could also be an example of API usage) hence I would prefer a dafny doc tool to define it by itself for the format of docstring it wants to support.

davidcok
davidcok previously approved these changes Mar 21, 2023
Copy link
Collaborator

@davidcok davidcok left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll likely have more comments when I actually use it.

@MikaelMayer MikaelMayer merged commit 0ef835d into master Mar 22, 2023
@MikaelMayer MikaelMayer deleted the feat-docstring branch March 22, 2023 15:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants