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: Standard library support + DafnyStdLibs.Wrappers #4678

Merged
merged 56 commits into from
Oct 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
07c0eb4
First pass at adding build support
robin-aws Oct 11, 2023
a8e2ca0
More C# progress, try out Java
robin-aws Oct 11, 2023
4bdaa29
Leave compilation out of scope for now
robin-aws Oct 13, 2023
aac5c7e
Get basic testing working
robin-aws Oct 13, 2023
6a7d06e
Rename, work around Go bug
robin-aws Oct 13, 2023
a20ddb6
Hook up option
robin-aws Oct 15, 2023
e854330
Off by default in tests
robin-aws Oct 16, 2023
0d4e4c4
Add documentation and hook up check-examples
robin-aws Oct 17, 2023
74b4ea0
Disable by default in general for now
robin-aws Oct 17, 2023
0af73be
Tweaks
robin-aws Oct 17, 2023
c47f90f
Temporarily checking in stdlib.doo
robin-aws Oct 17, 2023
bd2dae0
Better default make target
robin-aws Oct 17, 2023
9fba8ff
Add CI
robin-aws Oct 17, 2023
3c3ccd1
Working directory for make
robin-aws Oct 18, 2023
bcdfe06
Whitespace
robin-aws Oct 18, 2023
8e128cd
PR feedback
robin-aws Oct 18, 2023
26b5a62
Typo in directory
robin-aws Oct 18, 2023
fdce3d6
Add link to check-examples
robin-aws Oct 18, 2023
30cdf42
Do not let documentSymbol API return a faulty line (#4675)
keyboardDrummer Oct 18, 2023
bc1bd5e
Allow not migrating legacy symbols (#4674)
keyboardDrummer Oct 18, 2023
1c0fd3a
Move lit tests so no symlink is needed (#4615)
keyboardDrummer Oct 18, 2023
aa9cc46
Off by default in tests
robin-aws Oct 16, 2023
3971c53
Add documentation and hook up check-examples
robin-aws Oct 17, 2023
3cf5a63
PR feedback
robin-aws Oct 18, 2023
69212b7
Fix bad merges
robin-aws Oct 18, 2023
d8df577
Merge branch 'master' into standard-library-support-verification-only
robin-aws Oct 18, 2023
9b6a65b
Restore actual implementation lost in merge
robin-aws Oct 18, 2023
3122b1e
Merge branch 'master' into standard-library-support-verification-only
robin-aws Oct 18, 2023
02d91bf
Ignore build directory, redirect all build output there
robin-aws Oct 18, 2023
9971ffe
Install bignumber in CI check
robin-aws Oct 18, 2023
0b4c6de
Whitespace
robin-aws Oct 18, 2023
5a9a38d
Merge branch 'standard-library-support-verification-only' of https://…
robin-aws Oct 18, 2023
501388e
Fix path to dafny in check-examples
robin-aws Oct 18, 2023
c791652
Debugging check-examples in CI
robin-aws Oct 18, 2023
26e62ff
Rename doo file
robin-aws Oct 18, 2023
dd29872
Fix .doo file rebuild check
robin-aws Oct 18, 2023
3968357
Integrations, loosening doo file checks
robin-aws Oct 18, 2023
28a7e64
Use stdlibs:/// URIs for standard library files
robin-aws Oct 19, 2023
87b6e6a
Move binary into binaries
robin-aws Oct 19, 2023
3b2270b
Rename “stdlibs” protocol to “dllresource”
robin-aws Oct 19, 2023
08688f9
Fix poor error message from dafny format —allow-standard-libraries
robin-aws Oct 19, 2023
cb84222
Rename to --standard-libraries
robin-aws Oct 19, 2023
99253d3
Add user- and contributor-facing docs
robin-aws Oct 19, 2023
4af5778
User guide
robin-aws Oct 19, 2023
cbe7380
Release note
robin-aws Oct 19, 2023
4c767ee
More options
robin-aws Oct 19, 2023
2310e37
Merge branch 'master' of https://github.com/dafny-lang/dafny into sta…
robin-aws Oct 19, 2023
65f7461
More options, a few more contributing notes
robin-aws Oct 19, 2023
7a76a41
Whitespace
robin-aws Oct 19, 2023
9f19dbd
Don’t be so strict on Uri schemes for now
robin-aws Oct 19, 2023
955e397
Fix bad merge
robin-aws Oct 20, 2023
2b1ee67
Final PR feedback
robin-aws Oct 20, 2023
3297934
src/dafnystdlibs -> src/DafnyStdLibs
robin-aws Oct 20, 2023
ad5f6c4
Merge branch 'master' of https://github.com/dafny-lang/dafny into sta…
robin-aws Oct 20, 2023
128cea4
Merge branch 'master' into standard-library-support-verification-only
robin-aws Oct 20, 2023
dd69cf8
Merge branch 'master' into standard-library-support-verification-only
robin-aws Oct 20, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .github/workflows/standard-libraries.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Build and Test Dafny Standard Libraries

on:
workflow_dispatch:
pull_request:
branches: [ master, main-* ]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
check-deep-tests:
uses: ./.github/workflows/check-deep-tests-reusable.yml

build:
needs: check-deep-tests
runs-on: ubuntu-latest

steps:
- name: Checkout Dafny
uses: actions/checkout@v3
with:
submodules: true
- name: Set up JDK 18
uses: actions/setup-java@v3
with:
java-version: 18
distribution: corretto
- name: Build Dafny
run: dotnet build Source/Dafny.sln
- name: Get Z3
run: make z3-ubuntu
- run: npm install bignumber.js
- name: Test DafnyStandardLibraries
run: make -C Source/DafnyStandardLibraries all

5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ Source/.vs
# Generated by VS Code
.vscode/*

# Generated by Dafny tooling
/Source/DafnyStandardLibraries/build

# Generated by Java tools (gradle/javac/etc)

/Source/DafnyRuntime/DafnyRuntimeJava/.gradle
Expand All @@ -70,3 +73,5 @@ package-lock.json
docs/dev/*.fix
docs/dev/*.feat
docs/dev/*.break


1 change: 1 addition & 0 deletions Source/DafnyCore/AST/Grammar/Printer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1236,6 +1236,7 @@ public void PrintStatement(Statement stmt, int indent) {
} else if (expectStmt != null && expectStmt.Message != null) {
wr.Write(", ");
PrintExpression(expectStmt.Message, true);
wr.Write(";");
} else {
wr.Write(";");
}
Expand Down
5 changes: 3 additions & 2 deletions Source/DafnyCore/AST/Grammar/ProgramParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,12 @@ public virtual Program ParseFiles(string programName, IReadOnlyList<DafnyFile> f
CancellationToken cancellationToken) {
var options = errorReporter.Options;
var builtIns = new SystemModuleManager(options);
var defaultModule = new DefaultModuleDefinition(files.Where(f => !f.IsPreverified).Select(f => f.Uri).ToList());
robin-aws marked this conversation as resolved.
Show resolved Hide resolved
var defaultModule = new DefaultModuleDefinition();

var rootSourceUris = files.Select(f => f.Uri).ToList();
var verifiedRoots = files.Where(df => df.IsPreverified).Select(df => df.Uri).ToHashSet();
var compiledRoots = files.Where(df => df.IsPrecompiled).Select(df => df.Uri).ToHashSet();
var compilation = new CompilationData(errorReporter, defaultModule.Includes, defaultModule.RootSourceUris, verifiedRoots,
var compilation = new CompilationData(errorReporter, defaultModule.Includes, rootSourceUris, verifiedRoots,
compiledRoots);
var program = new Program(
programName,
Expand Down
5 changes: 1 addition & 4 deletions Source/DafnyCore/AST/Modules/DefaultModuleDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,13 @@ namespace Microsoft.Dafny;

public class DefaultModuleDefinition : ModuleDefinition, ICloneable<DefaultModuleDefinition> {
public List<Include> Includes { get; } = new();
public IList<Uri> RootSourceUris { get; }

public DefaultModuleDefinition(Cloner cloner, DefaultModuleDefinition original) : base(cloner, original, original.NameNode) {
RootSourceUris = original.RootSourceUris;
}

public DefaultModuleDefinition(IList<Uri> rootSourceUris)
public DefaultModuleDefinition()
: base(RangeToken.NoToken, new Name("_module"), new List<IToken>(), false, false,
null, null, null) {
RootSourceUris = rootSourceUris;
}

public override bool IsDefaultModule => true;
Expand Down
2 changes: 1 addition & 1 deletion Source/DafnyCore/AST/Statements/Methods/PrintStmt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ static PrintStmt() {

DooFile.RegisterLibraryChecks(
checks: new Dictionary<Option, DooFile.OptionCheck> {
{ TrackPrintEffectsOption, DooFile.CheckOptionMatches }
{ TrackPrintEffectsOption, DooFile.CheckOptionLocalImpliesLibrary }
});
}

Expand Down
3 changes: 3 additions & 0 deletions Source/DafnyCore/Compilers/Library/LibraryBackend.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ public override string TargetBaseDir(string dafnyProgramName) =>
Feature.LegacyCLI
};

// Necessary since Compiler is null
public override string ModuleSeparator => ".";

protected override SinglePassCompiler CreateCompiler() {
return null;
}
Expand Down
53 changes: 37 additions & 16 deletions Source/DafnyCore/DafnyFile.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using DafnyCore;
Expand All @@ -10,6 +11,7 @@ namespace Microsoft.Dafny;

public class DafnyFile {
public string FilePath { get; private set; }
public string Extension { get; private set; }
public string CanonicalPath { get; private set; }
public string BaseName { get; private set; }
public bool IsPreverified { get; set; }
Expand All @@ -23,29 +25,33 @@ public DafnyFile(DafnyOptions options, Uri uri, [CanBeNull] IToken origin = null
Origin = origin;
var filePath = uri.LocalPath;

var extension = ".dfy";
Extension = ".dfy";
if (uri.IsFile) {
extension = Path.GetExtension(uri.LocalPath).ToLower();
Extension = Path.GetExtension(uri.LocalPath).ToLower();
BaseName = Path.GetFileName(uri.LocalPath);
}
if (uri.Scheme == "stdin") {
// Normalizing symbolic links appears to be not
// supported in .Net APIs, because it is very difficult in general
// So we will just use the absolute path, lowercased for all file systems.
// cf. IncludeComparer.CompareTo
CanonicalPath = Canonicalize(filePath).LocalPath;
} else if (uri.Scheme == "stdin") {
getContentOverride = () => options.Input;
BaseName = "<stdin>";
CanonicalPath = "<stdin>";
} else if (uri.Scheme == "dllresource") {
Extension = Path.GetExtension(uri.LocalPath).ToLower();
BaseName = uri.LocalPath;
CanonicalPath = uri.ToString();
}

// Normalizing symbolic links appears to be not
// supported in .Net APIs, because it is very difficult in general
// So we will just use the absolute path, lowercased for all file systems.
// cf. IncludeComparer.CompareTo
CanonicalPath = getContentOverride == null ? Canonicalize(filePath).LocalPath : "<stdin>";
FilePath = CanonicalPath;

var filePathForErrors = options.UseBaseNameForFileName ? Path.GetFileName(filePath) : filePath;
if (getContentOverride != null) {
IsPreverified = false;
IsPrecompiled = false;
GetContent = getContentOverride;
} else if (extension == ".dfy" || extension == ".dfyi") {
} else if (Extension == ".dfy" || Extension == ".dfyi") {
IsPreverified = false;
IsPrecompiled = false;
if (!File.Exists(filePath)) {
Expand All @@ -61,15 +67,30 @@ public DafnyFile(DafnyOptions options, Uri uri, [CanBeNull] IToken origin = null
} else {
GetContent = () => new StreamReader(filePath);
}
} else if (extension == ".doo") {
} else if (Extension == ".doo") {
IsPreverified = true;
IsPrecompiled = false;

if (!File.Exists(filePath)) {
options.Printer.ErrorWriteLine(options.OutputWriter, $"*** Error: file {filePathForErrors} not found");
throw new IllegalDafnyFile(true);
DooFile dooFile;
if (uri.Scheme == "dllresource") {
var assembly = Assembly.Load(uri.Host);
// Skip the leading "/"
var resourceName = uri.LocalPath[1..];
using var stream = assembly.GetManifestResourceStream(resourceName);
if (stream is null) {
throw new Exception($"Cannot find embedded resource: {resourceName}");
}

dooFile = DooFile.Read(stream);
} else {
if (!File.Exists(filePath)) {
options.Printer.ErrorWriteLine(options.OutputWriter, $"*** Error: file {filePathForErrors} not found");
throw new IllegalDafnyFile(true);
}

dooFile = DooFile.Read(filePath);
}
var dooFile = DooFile.Read(filePath);

if (!dooFile.Validate(filePathForErrors, options, options.CurrentCommand)) {
throw new IllegalDafnyFile(true);
}
Expand All @@ -81,7 +102,7 @@ public DafnyFile(DafnyOptions options, Uri uri, [CanBeNull] IToken origin = null
// the DooFile class should encapsulate the serialization logic better
// and expose a Program instead of the program text.
GetContent = () => new StringReader(dooFile.ProgramText);
} else if (extension == ".dll") {
} else if (Extension == ".dll") {
IsPreverified = true;
// Technically only for C#, this is for backwards compatability
IsPrecompiled = true;
Expand Down
45 changes: 42 additions & 3 deletions Source/DafnyCore/DooFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,18 @@ public void Write(TextWriter writer) {
private static DafnyOptions ProgramSerializationOptions => DafnyOptions.Default;

public static DooFile Read(string path) {
using var archive = ZipFile.Open(path, ZipArchiveMode.Read);
return Read(archive);
}

public static DooFile Read(Stream stream) {
using var archive = new ZipArchive(stream);
return Read(archive);
}

private static DooFile Read(ZipArchive archive) {
var result = new DooFile();

using var archive = ZipFile.Open(path, ZipArchiveMode.Read);
var manifestEntry = archive.GetEntry(ManifestFileEntry);
if (manifestEntry == null) {
throw new ArgumentException(".doo file missing manifest entry");
Expand Down Expand Up @@ -119,12 +128,12 @@ public bool Validate(string filePath, DafnyOptions options, Command currentComma
}

var success = true;
var revelantOptions = currentCommand.Options.ToHashSet();
var relevantOptions = currentCommand.Options.ToHashSet();
foreach (var (option, check) in OptionChecks) {
// It's important to only look at the options the current command uses,
// because other options won't be initialized to the correct default value.
// See CommandRegistry.Create().
if (!revelantOptions.Contains(option)) {
if (!relevantOptions.Contains(option)) {
continue;
}

Expand Down Expand Up @@ -212,6 +221,30 @@ public static bool CheckOptionMatches(DafnyOptions options, Option option, objec
return false;
}

/// Checks that the library option ==> the local option.
/// E.g. --no-verify: the only incompatibility is if it's on in the library but not locally.
/// Generally the right check for options that weaken guarantees.
public static bool CheckOptionLibraryImpliesLocal(DafnyOptions options, Option option, object localValue, string libraryFile, object libraryValue) {
if (OptionValuesImplied(option, libraryValue, localValue)) {
return true;
}

options.Printer.ErrorWriteLine(options.OutputWriter, $"*** Error: Cannot load {libraryFile}: --{option.Name} is set locally to {OptionValueToString(option, localValue)}, but the library was built with {OptionValueToString(option, libraryValue)}");
return false;
}

/// Checks that the local option ==> the library option.
/// E.g. --track-print-effects: the only incompatibility is if it's on locally but not in the library.
/// Generally the right check for options that strengthen guarantees.
public static bool CheckOptionLocalImpliesLibrary(DafnyOptions options, Option option, object localValue, string libraryFile, object libraryValue) {
if (OptionValuesImplied(option, localValue, libraryValue)) {
return true;
}

options.Printer.ErrorWriteLine(options.OutputWriter, $"*** Error: Cannot load {libraryFile}: --{option.Name} is set locally to {OptionValueToString(option, localValue)}, but the library was built with {OptionValueToString(option, libraryValue)}");
return false;
}

private static bool OptionValuesEqual(Option option, object first, object second) {
if (first.Equals(second)) {
return true;
Expand All @@ -224,6 +257,12 @@ private static bool OptionValuesEqual(Option option, object first, object second
return false;
}

private static bool OptionValuesImplied(Option option, object first, object second) {
var lhs = (bool)first;
var rhs = (bool)second;
return !lhs || rhs;
}

private static string OptionValueToString(Option option, object value) {
if (option.ValueType == typeof(IEnumerable<string>)) {
var values = (IEnumerable<string>)value;
Expand Down
2 changes: 1 addition & 1 deletion Source/DafnyCore/Options/BoogieOptionBag.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ static BoogieOptionBag() {
new Dictionary<Option, DooFile.OptionCheck> {
{ BoogieArguments, DooFile.CheckOptionMatches },
{ BoogieFilter, DooFile.CheckOptionMatches },
{ NoVerify, DooFile.CheckOptionMatches },
{ NoVerify, DooFile.CheckOptionLibraryImpliesLocal },
}
);
DooFile.RegisterNoChecksNeeded(
Expand Down
27 changes: 14 additions & 13 deletions Source/DafnyCore/Options/CommonOptionBag.cs
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,13 @@ public enum DefaultFunctionOpacityOptions {
`opaque` means functions are always opaque so the opaque keyword is not needed, and functions must be revealed everywhere needed for a proof.".TrimStart()) {
};

public static readonly Option<bool> UseStandardLibraries = new("--standard-libraries", () => false,
@"
Allow Dafny code to depend on the standard libraries included with the Dafny distribution.
See https://github.com/dafny-lang/dafny/blob/master/Source/DafnyStandardLibraries/README.md for more information.
Not compatible with the --unicode-char:false option.
");

static CommonOptionBag() {
DafnyOptions.RegisterLegacyUi(Target, DafnyOptions.ParseString, "Compilation options", "compileTarget", @"
cs (default) - Compile to .NET via C#.
Expand Down Expand Up @@ -438,18 +445,9 @@ void ParsePrintMode(Option<PrintModes> option, Boogie.CommandLineParseState ps,
DooFile.RegisterLibraryChecks(
new Dictionary<Option, DooFile.OptionCheck>() {
{ UnicodeCharacters, DooFile.CheckOptionMatches },
{ EnforceDeterminism, DooFile.CheckOptionMatches },
{ RelaxDefiniteAssignment, DooFile.CheckOptionMatches },
// Ideally this feature shouldn't affect separate compilation,
// because it's automatically disabled on {:extern} signatures.
// Realistically though, we don't have enough strong mechanisms to stop
// target language code from referencing compiled internal code,
// so to be conservative we flag this as not compatible in general.
{ OptimizeErasableDatatypeWrapper, DooFile.CheckOptionMatches },
// Similarly this shouldn't matter if external code ONLY refers to {:extern}s,
// but in practice it does.
{ AddCompileSuffix, DooFile.CheckOptionMatches },
{ ReadsClausesOnMethods, DooFile.CheckOptionMatches },
{ EnforceDeterminism, DooFile.CheckOptionLocalImpliesLibrary },
{ RelaxDefiniteAssignment, DooFile.CheckOptionLibraryImpliesLocal },
{ ReadsClausesOnMethods, DooFile.CheckOptionLocalImpliesLibrary },
}
);
DooFile.RegisterNoChecksNeeded(
Expand Down Expand Up @@ -485,7 +483,10 @@ void ParsePrintMode(Option<PrintModes> option, Boogie.CommandLineParseState ps,
WarnRedundantAssumptions,
VerificationCoverageReport,
NoTimeStampForCoverageReport,
DefaultFunctionOpacity
DefaultFunctionOpacity,
UseStandardLibraries,
OptimizeErasableDatatypeWrapper,
AddCompileSuffix
);
}

Expand Down
3 changes: 2 additions & 1 deletion Source/DafnyCore/Options/DafnyCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@ static DafnyCommands() {
CommonOptionBag.TypeSystemRefresh,
CommonOptionBag.TypeInferenceDebug,
CommonOptionBag.NewTypeInferenceDebug,
CommonOptionBag.ReadsClausesOnMethods
CommonOptionBag.ReadsClausesOnMethods,
CommonOptionBag.UseStandardLibraries
});

public static IReadOnlyList<Option> ResolverOptions = new List<Option>(new Option[] {
Expand Down
10 changes: 10 additions & 0 deletions Source/DafnyDriver/Commands/DafnyCli.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ public static class DafnyCli {
private const string ToolchainDebuggingHelpName = "--help-internal";
private static readonly RootCommand RootCommand = new("The Dafny CLI enables working with Dafny, a verification-aware programming language. Use 'dafny -?' to see help for the previous CLI format.");

private static readonly Uri StandardLibrariesDooUri = new("dllresource://DafnyPipeline/DafnyStandardLibraries.doo");

public static int Main(string[] args) {
return MainWithWriters(Console.Out, Console.Error, Console.In, args);
}
Expand Down Expand Up @@ -481,6 +483,14 @@ public static ExitValue GetDafnyFiles(DafnyOptions options,
return ExitValue.PREPROCESSING_ERROR;
}

// Add standard library .doo files after explicitly provided source files,
// only because if they are added first, one might be used as the program name,
// which is not handled well.
if (options.Get(CommonOptionBag.UseStandardLibraries)) {
options.CliRootSourceUris.Add(StandardLibrariesDooUri);
Copy link
Member

Choose a reason for hiding this comment

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

Awkward that you need both lines.

Copy link
Member Author

Choose a reason for hiding this comment

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

Agreed, same goes for the existing stdin: protocol that I'm imitating.

Copy link
Member

Choose a reason for hiding this comment

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

If you want this to work for dafny server, you will need to update DafnyProject.GetRootSourceUris so it returns StandardLibrariesDooUri.

Alternatively, I can do the refactoring to reuse more of the server code for the CLI and then this'll come for free.

Copy link
Member Author

@robin-aws robin-aws Oct 19, 2023

Choose a reason for hiding this comment

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

I'd be very happy will letting your refactoring take care of this - I'm fine with the examples and tests in DafnyStandardLibraries not resolving in the IDE initially, since that doesn't block adding new libraries, but would love to fast-follow with fixing that. I suspect that refactoring will fix the awkwardness too since the DafnyFile will be added from the root URI automatically?

dafnyFiles.Add(new DafnyFile(options, StandardLibrariesDooUri));
}

return ExitValue.SUCCESS;
}

Expand Down
Loading
Loading