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

Support all option types in the project file #4506

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
0beaffb
Support all option types in the project file
keyboardDrummer Sep 1, 2023
4946ac6
Merge branch 'master' into supportAllOptionTypesInProjectFile
keyboardDrummer Sep 7, 2023
7a6914d
Add release note and fix path resolution in Dafny project file
keyboardDrummer Sep 7, 2023
81c7863
Merge branch 'supportAllOptionTypesInProjectFile' of github.com:keybo…
keyboardDrummer Sep 7, 2023
8b3b1ee
Merge branch 'master' into supportAllOptionTypesInProjectFile
keyboardDrummer Sep 7, 2023
32c61ba
Merge branch 'master' into supportAllOptionTypesInProjectFile
keyboardDrummer Sep 7, 2023
e913591
Fixes for using library in options
keyboardDrummer Sep 8, 2023
c89fd32
Update release notes
keyboardDrummer Sep 8, 2023
327817f
Merge branch 'master' into supportAllOptionTypesInProjectFile
keyboardDrummer Sep 8, 2023
8df76c8
Run formatter
keyboardDrummer Sep 8, 2023
9e29a2a
Merge branch 'supportAllOptionTypesInProjectFile' of github.com:keybo…
keyboardDrummer Sep 8, 2023
7dde794
More fixes
keyboardDrummer Sep 8, 2023
19ca042
Merge branch 'master' into supportAllOptionTypesInProjectFile
keyboardDrummer Sep 8, 2023
177f6cb
Trigger CI
keyboardDrummer Sep 10, 2023
3146dc2
Merge branch 'supportAllOptionTypesInProjectFile' of github.com:keybo…
keyboardDrummer Sep 10, 2023
e6a4a50
usesLibrary test now passes
keyboardDrummer Sep 11, 2023
d52dd2c
Merge branch 'master' into supportAllOptionTypesInProjectFile
keyboardDrummer Sep 11, 2023
e7c0a23
Merge branch 'master' into supportAllOptionTypesInProjectFile
keyboardDrummer Sep 11, 2023
282a0ec
Merge branch 'master' into supportAllOptionTypesInProjectFile
keyboardDrummer Sep 11, 2023
5e499c2
Make test more stable
keyboardDrummer Sep 12, 2023
bbab8ae
Merge branch 'master' into supportAllOptionTypesInProjectFile
keyboardDrummer Sep 12, 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
12 changes: 6 additions & 6 deletions Source/DafnyCore.Test/DafnyProjectTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public class DafnyProjectTest {
Uri = new Uri(randomFileName, UriKind.Absolute),
Includes = new[] { "a", "a2" },
Excludes = new[] { "b", "b2" },
Options = new Dictionary<string, object>() {
Options = new Dictionary<string, string>() {
{ "c", "d" },
{ "e", "f" }
}
Expand All @@ -20,21 +20,21 @@ public class DafnyProjectTest {
Uri = new Uri(randomFileName, UriKind.Absolute),
Includes = new[] { "a2", "a" },
Excludes = new[] { "b2", "b" },
Options = new Dictionary<string, object>() {
Options = new Dictionary<string, string>() {
{ "e", "f" },
{ "c", "d" },
}
};

Assert.Equal(first, second);

first.Options.Add("k", new[] { 1, 2, 3 });
second.Options.Add("k", new[] { 1, 2, 3 });
first.Options.Add("k", "1, 2, 3");
second.Options.Add("k", "1, 2, 3");

Assert.Equal(first, second);

first.Options.Add("m", new[] { 1, 2, 3 });
second.Options.Add("m", new[] { 3, 2, 1 });
first.Options.Add("m", "1, 2, 3");
second.Options.Add("m", "3, 2, 1");
Assert.NotEqual(first, second);
}
}
9 changes: 2 additions & 7 deletions Source/DafnyCore/DooFile.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
using System;
using System.Collections.Generic;
using System.CommandLine;
using System.CommandLine.Binding;
using System.Configuration;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Threading;
using DafnyCore.Generic;
using Microsoft.Dafny;
using Microsoft.Dafny.Auditor;
using Tomlyn;
using Tomlyn.Model;
using Type = System.Type;

namespace DafnyCore;

Expand Down Expand Up @@ -137,7 +132,7 @@ public class ManifestData {

object libraryValue = null;
if (Manifest.Options.TryGetValue(option.Name, out var manifestValue)) {
if (!DafnyProject.TryGetValueFromToml(Console.Out, null,
if (!TomlUtil.TryGetValueFromToml(Console.Out, null,
option.Name, option.ValueType, manifestValue, out libraryValue)) {
return false;
}
Expand Down
70 changes: 70 additions & 0 deletions Source/DafnyCore/Generic/TomlUtil.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Tomlyn.Model;

namespace DafnyCore.Generic;

public static class TomlUtil {

public static bool TryGetValueFromToml(TextWriter errorWriter, string sourceDir, string tomlPath, Type type, object tomlValue, out object value) {
if (tomlValue == null) {
value = null;
return false;
}

if (type.IsAssignableFrom(typeof(List<string>))) {
return TryGetListValueFromToml<string>(errorWriter, sourceDir, tomlPath, (TomlArray)tomlValue, out value);
}
if (type.IsAssignableFrom(typeof(List<FileInfo>))) {
return TryGetListValueFromToml<FileInfo>(errorWriter, sourceDir, tomlPath, (TomlArray)tomlValue, out value);
}

if (tomlValue is string tomlString) {
if (type == typeof(FileInfo)) {
// Need to make sure relative paths are interpreted relative to the source of the value,
// not the current directory.
var fullPath = sourceDir != null ? Path.GetFullPath(tomlString, sourceDir) : tomlString;
value = new FileInfo(fullPath);
return true;
}

if (typeof(Enum).IsAssignableFrom(type)) {
try {
value = Enum.Parse(type, tomlString);
return true;
} catch (ArgumentException) {
value = null;
return false;
}
}
}

if (!type.IsInstanceOfType(tomlValue)) {
if (type == typeof(string)) {
value = tomlValue.ToString();
return true;
}
errorWriter.WriteLine(
$"Error: property '{tomlPath}' is of type '{tomlValue.GetType()}' but should be of type '{type}'");
value = null;
return false;
}

value = tomlValue;
return true;
}

private static bool TryGetListValueFromToml<T>(TextWriter errorWriter, string sourceDir, string tomlPath, TomlArray tomlValue, out object value) {
var success = true;
value = tomlValue.Select((e, i) => {
if (TryGetValueFromToml(errorWriter, sourceDir, $"{tomlPath}[{i}]", typeof(T), e, out var elementValue)) {
return (T)elementValue;
}
success = false;
return default(T);
}).ToList();
return success;
}
}
56 changes: 7 additions & 49 deletions Source/DafnyCore/Options/DafnyProject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
using DafnyCore.Options;
using Microsoft.Extensions.FileSystemGlobbing;
using Tomlyn;
using Tomlyn.Model;

namespace Microsoft.Dafny;

Expand All @@ -23,7 +22,7 @@ public class DafnyProject : IEquatable<DafnyProject> {
public Uri Uri { get; set; }
public string[] Includes { get; set; }
public string[] Excludes { get; set; }
public Dictionary<string, object> Options { get; set; }
public Dictionary<string, string> Options { get; set; }
public bool UsesProjectFile => Path.GetFileName(Uri.LocalPath) == FileName;

public static async Task<DafnyProject> Open(IFileSystem fileSystem, Uri uri, TextWriter outputWriter, TextWriter errorWriter) {
Expand Down Expand Up @@ -140,57 +139,16 @@ public class DafnyProject : IEquatable<DafnyProject> {
return false;
}

return TryGetValueFromToml(errorWriter, Path.GetDirectoryName(Uri.LocalPath), option.Name, option.ValueType, tomlValue, out value);
}

public static bool TryGetValueFromToml(TextWriter errorWriter, string sourceDir, string tomlPath, System.Type type, object tomlValue, out object value) {
if (tomlValue == null) {
value = null;
return false;
}

if (type.IsAssignableFrom(typeof(List<string>))) {
return TryGetListValueFromToml<string>(errorWriter, sourceDir, tomlPath, (TomlArray)tomlValue, out value);
}
if (type.IsAssignableFrom(typeof(List<FileInfo>))) {
return TryGetListValueFromToml<FileInfo>(errorWriter, sourceDir, tomlPath, (TomlArray)tomlValue, out value);
}

if (type == typeof(FileInfo) && tomlValue is string tomlString) {
// Need to make sure relative paths are interpreted relative to the source of the value,
// not the current directory.
var fullPath = sourceDir != null ? Path.GetFullPath(tomlString, sourceDir) : tomlString;
value = new FileInfo(fullPath);
return true;
}

if (!type.IsInstanceOfType(tomlValue)) {
if (type == typeof(string)) {
value = tomlValue.ToString();
return true;
}
errorWriter.WriteLine(
$"Error: property '{tomlPath}' is of type '{tomlValue.GetType()}' but should be of type '{type}'");
var parseResult = option.Parse(new[] { option.Aliases.First(), tomlValue });
Copy link
Member Author

Choose a reason for hiding this comment

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

This will parse the project file options in the same way as the CLI parses them, so there won't be any discrepancy any more.

if (parseResult.Errors.Any()) {
value = null;
return false;
}

value = tomlValue;
value = parseResult.GetValueForOption(option);
return true;
}

private static bool TryGetListValueFromToml<T>(TextWriter errorWriter, string sourceDir, string tomlPath, TomlArray tomlValue, out object value) {
var success = true;
value = tomlValue.Select((e, i) => {
if (TryGetValueFromToml(errorWriter, sourceDir, $"{tomlPath}[{i}]", typeof(T), e, out var elementValue)) {
return (T)elementValue;
}
success = false;
return default(T);
}).ToList();
return success;
}

public bool Equals(DafnyProject other) {
if (ReferenceEquals(null, other)) {
return false;
Expand All @@ -200,13 +158,13 @@ public class DafnyProject : IEquatable<DafnyProject> {
return true;
}

var orderedOptions = Options?.OrderBy(kv => kv.Key) ?? Enumerable.Empty<KeyValuePair<string, object>>();
var otherOrderedOptions = other.Options?.OrderBy(kv => kv.Key) ?? Enumerable.Empty<KeyValuePair<string, object>>();
var orderedOptions = Options?.OrderBy(kv => kv.Key) ?? Enumerable.Empty<KeyValuePair<string, string>>();
var otherOrderedOptions = other.Options?.OrderBy(kv => kv.Key) ?? Enumerable.Empty<KeyValuePair<string, string>>();

return Equals(Uri, other.Uri) &&
NullableSetEqual(Includes?.ToHashSet(), other.Includes) &&
NullableSetEqual(Excludes?.ToHashSet(), other.Excludes) &&
orderedOptions.SequenceEqual(otherOrderedOptions, new LambdaEqualityComparer<KeyValuePair<string, object>>(
orderedOptions.SequenceEqual(otherOrderedOptions, new LambdaEqualityComparer<KeyValuePair<string, string>>(
(kv1, kv2) => kv1.Key == kv2.Key && GenericEquals(kv1.Value, kv2.Value),
kv => kv.GetHashCode()));
}
Expand Down
11 changes: 10 additions & 1 deletion Source/DafnyLanguageServer.Test/ProjectFiles/ProjectFilesTest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Dafny.LanguageServer.IntegrationTest.Extensions;
using Microsoft.Dafny.LanguageServer.IntegrationTest.Util;
Expand Down Expand Up @@ -67,6 +68,7 @@ public class ProjectFilesTest : ClientBasedLanguageServerTest {
public async Task ProjectFileOverridesOptions() {
await SetUp(options => {
options.Set(Function.FunctionSyntaxOption, "3");
options.Set(CommonOptionBag.QuantifierSyntax, QuantifierSyntaxOptions.Version3);
options.Set(CommonOptionBag.WarnShadowing, true);
});
var source = @"
Expand All @@ -77,13 +79,18 @@ public class ProjectFilesTest : ClientBasedLanguageServerTest {
}
}

function Zoo(): set<(int,int)> {
set x: int | 0 <= x < 5, y | 0 <= y < 6 :: (x,y)
}

ghost function Bar(): int { 3 }".TrimStart();

var projectFileSource = @"
includes = [""**/*.dfy""]

[options]
warn-shadowing = false
quantifier-syntax = 4
Copy link
Member

Choose a reason for hiding this comment

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

In your test, could you please add an option that was otherwise translated to an enum or otherwise processed?
For example:
test-assumptions = externs
It serves no purpose for your example other than testing that the parsing mechanism works (which it did not until this PR)

Copy link
Member Author

@keyboardDrummer keyboardDrummer Sep 2, 2023

Choose a reason for hiding this comment

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

quantifier-syntax is an enum type option, even though it looks like an int. If I just add test-assumptions = externs without testing its effects, then it's not testing much is it?

Copy link
Member

Choose a reason for hiding this comment

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

Oh great, you got my point and that's perfect then.

function-syntax = 4";

var directory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
Expand All @@ -94,7 +101,9 @@ public class ProjectFilesTest : ClientBasedLanguageServerTest {
Assert.Single(diagnostics1); // Stops after parsing

await File.WriteAllTextAsync(Path.Combine(directory, DafnyProject.FileName), projectFileSource);
await CreateAndOpenTestDocument(source, Path.Combine(directory, "source.dfy"));
var sourceFile = await CreateAndOpenTestDocument(source, Path.Combine(directory, "source.dfy"));
var diagnostics2 = await GetLastDiagnostics(sourceFile, CancellationToken);
Assert.Empty(diagnostics2.Where(d => d.Severity == DiagnosticSeverity.Error));
await AssertNoDiagnosticsAreComing(CancellationToken);
}

Expand Down
Loading