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

Fix C# codegen for tail-recursive member functions #2205

Merged
merged 7 commits into from
Jun 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
- fix: No more display of previous obsolete verification diagnostics if newer are available. (https://github.com/dafny-lang/dafny/pull/2142)
- feat: Live verification diagnostics for the language server (https://github.com/dafny-lang/dafny/pull/1942)
- fix: Prevent the language server from crashing and not responding on resolution or ghost diagnostics errors (https://github.com/dafny-lang/dafny/pull/2080)
- fix: Correctly specify the type of the receiver parameter when translating tail-recursive member functions to C# (https://github.com/dafny-lang/dafny/pull/2205)

# 3.6.0

Expand Down
45 changes: 24 additions & 21 deletions Source/Dafny/Compilers/Compiler-Csharp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,12 @@ protected override ConcreteSyntaxTree CreateIterator(IteratorDecl iter, Concrete
return beforeYield;
}

private string DtTypeName(TopLevelDecl dt, bool typeVariables = true) {
var name = "_I" + dt.CompileName;
if (typeVariables) { name += TypeParameters(SelectNonGhost(dt, dt.TypeArgs)); }
return name;
}

protected override IClassWriter/*?*/ DeclareDatatype(DatatypeDecl dt, ConcreteSyntaxTree wr) {
var w = CompileDatatypeBase(dt, wr);
CompileDatatypeConstructors(dt, wr);
Expand Down Expand Up @@ -472,13 +478,12 @@ IClassWriter CompileDatatypeBase(DatatypeDecl dt, ConcreteSyntaxTree wr) {
var nonGhostTypeArgs = SelectNonGhost(dt, dt.TypeArgs);
var DtT_TypeArgs = TypeParameters(nonGhostTypeArgs);
var DtT_protected = IdName(dt) + DtT_TypeArgs;
var IDtT_protected = "_I" + dt.CompileName + DtT_TypeArgs;

// ContreteSyntaxTree for the interface
var interfaceTree = wr.NewNamedBlock($"public interface _I{dt.CompileName}{TypeParameters(nonGhostTypeArgs, true)}");

// from here on, write everything into the new block created here:
var btw = wr.NewNamedBlock("public{0} class {1} : {2}", dt.IsRecordType ? "" : " abstract", DtT_protected, IDtT_protected);
var btw = wr.NewNamedBlock("public{0} class {1} : {2}", dt.IsRecordType ? "" : " abstract", DtT_protected, DtTypeName(dt));
wr = btw;

// constructor
Expand All @@ -490,12 +495,12 @@ IClassWriter CompileDatatypeBase(DatatypeDecl dt, ConcreteSyntaxTree wr) {

var wDefault = new ConcreteSyntaxTree();
if (nonGhostTypeArgs.Count == 0) {
wr.FormatLine($"private static readonly {IDtT_protected} theDefault = {wDefault};");
var w = wr.NewBlock($"public static {IDtT_protected} Default()");
wr.FormatLine($"private static readonly {DtTypeName(dt)} theDefault = {wDefault};");
var w = wr.NewBlock($"public static {DtTypeName(dt)} Default()");
w.WriteLine("return theDefault;");
} else {
var parameters = UsedTypeParameters(dt).Comma(tp => $"{tp.CompileName} {FormatDefaultTypeParameterValue(tp)}");
wr.Write($"public static {IDtT_protected} Default({parameters})");
wr.Write($"public static {DtTypeName(dt)} Default({parameters})");
var w = wr.NewBlock();
w.FormatLine($"return {wDefault};");
}
Expand All @@ -514,13 +519,13 @@ IClassWriter CompileDatatypeBase(DatatypeDecl dt, ConcreteSyntaxTree wr) {
EmitTypeDescriptorMethod(dt, wr);

if (dt is CoDatatypeDecl) {
interfaceTree.WriteLine($"{IDtT_protected} _Get();");
wr.WriteLine($"public abstract {IDtT_protected} _Get();");
interfaceTree.WriteLine($"{DtTypeName(dt)} _Get();");
wr.WriteLine($"public abstract {DtTypeName(dt)} _Get();");
}

// create methods
foreach (var ctor in dt.Ctors) {
wr.Write($"public static {IDtT_protected} {DtCreateName(ctor)}(");
wr.Write($"public static {DtTypeName(dt)} {DtCreateName(ctor)}(");
WriteFormals("", ctor.Formals, wr);
var w = wr.NewBlock(")");
var arguments = ctor.Formals.Where(f => !f.IsGhost).Comma(FormalName);
Expand All @@ -546,7 +551,7 @@ IClassWriter CompileDatatypeBase(DatatypeDecl dt, ConcreteSyntaxTree wr) {
if (dt.HasFinitePossibleValues) {
Contract.Assert(nonGhostTypeArgs.Count == 0);
var w = wr.NewNamedBlock(
$"public static System.Collections.Generic.IEnumerable<{IDtT_protected}> AllSingletonConstructors");
$"public static System.Collections.Generic.IEnumerable<{DtTypeName(dt)}> AllSingletonConstructors");
var wGet = w.NewBlock("get");
foreach (var ctor in dt.Ctors) {
Contract.Assert(ctor.Formals.Count == 0);
Expand Down Expand Up @@ -603,7 +608,7 @@ string Modifiers(string abs, string single, string cons) =>
}

if (!(toInterface && customReceiver)) {
var receiver = customReceiver ? $"{"_I" + datatype.CompileName + typeArgs} _this, " : "";
var receiver = customReceiver ? $"{DtTypeName(datatype)} _this, " : "";
wr.Write($"{dtArgs} DowncastClone{uTypeArgs}({receiver}{nonGhostTypeArgs.Comma(PrintConverter)})");
}

Expand Down Expand Up @@ -785,7 +790,7 @@ bool InvalidType(Type ty) => (ty.AsTypeParameter != null && ty.AsTypeParameter.E
}


void CompileDatatypeConstructors(DatatypeDecl dt, ConcreteSyntaxTree wrx) {
private void CompileDatatypeConstructors(DatatypeDecl dt, ConcreteSyntaxTree wrx) {
Contract.Requires(dt != null);
var nonGhostTypeArgs = SelectNonGhost(dt, dt.TypeArgs);
string typeParams = TypeParameters(nonGhostTypeArgs);
Expand All @@ -803,13 +808,12 @@ void CompileDatatypeConstructors(DatatypeDecl dt, ConcreteSyntaxTree wrx) {
// public override string ToString() { return _Get().ToString(); }
// }
var w = wrx.NewNamedBlock($"public class {dt.CompileName}__Lazy{typeParams} : {IdName(dt)}{typeParams}");
var ICompileName = "_I" + dt.CompileName;
w.WriteLine($"public {NeedsNew(dt, "Computer")}delegate {ICompileName}{typeParams} Computer();");
w.WriteLine($"public {NeedsNew(dt, "Computer")}delegate {DtTypeName(dt)} Computer();");
w.WriteLine($"{NeedsNew(dt, "c")}Computer c;");
w.WriteLine($"{NeedsNew(dt, "d")}{ICompileName}{typeParams} d;");
w.WriteLine($"{NeedsNew(dt, "d")}{DtTypeName(dt)} d;");
w.WriteLine($"public {dt.CompileName}__Lazy(Computer c) {{ this.c = c; }}");
CompileDatatypeDowncastClone(dt, w, nonGhostTypeArgs, lazy: true);
w.WriteLine($"public override {ICompileName}{typeParams} _Get() {{ if (c != null) {{ d = c(); c = null; }} return d; }}");
w.WriteLine($"public override {DtTypeName(dt)} _Get() {{ if (c != null) {{ d = c(); c = null; }} return d; }}");
w.WriteLine("public override string ToString() { return _Get().ToString(); }");
}

Expand Down Expand Up @@ -882,8 +886,7 @@ void DatatypeFieldsAndConstructor(DatatypeCtor ctor, int constructorIndex, Concr
var nonGhostTypeArgs = SelectNonGhost(dt, dt.TypeArgs);

if (dt is CoDatatypeDecl) {
string typeParams = TypeParameters(nonGhostTypeArgs);
wr.WriteLine($"public override _I{dt.CompileName}{typeParams} _Get() {{ return this; }}");
wr.WriteLine($"public override {DtTypeName(dt)} _Get() {{ return this; }}");
}

CompileDatatypeDowncastClone(dt, wr, nonGhostTypeArgs, ctor: ctor);
Expand Down Expand Up @@ -1329,9 +1332,10 @@ public bool ProcessDllImport(MemberDecl decl, ConcreteSyntaxTree wr) {
}

protected override ConcreteSyntaxTree EmitTailCallStructure(MemberDecl member, ConcreteSyntaxTree wr) {
Contract.Assume((member is Method m0 && m0.IsTailRecursive) || (member is Function f0 && f0.IsTailRecursive)); // precondition
Contract.Assume(member is Method { IsTailRecursive: true } or Function { IsTailRecursive: true }); // precondition
if (!member.IsStatic && !NeedsCustomReceiver(member)) {
wr.WriteLine("var _this = this;");
var receiverType = member.EnclosingClass is DatatypeDecl dt ? DtTypeName(dt) : "var";
wr.WriteLine($"{receiverType} _this = this;");
Comment on lines +1337 to +1338
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
var receiverType = member.EnclosingClass is DatatypeDecl dt ? DtTypeName(dt) : "var";
wr.WriteLine($"{receiverType} _this = this;");
wr.Write(member.EnclosingClass is DatatypeDecl dt ? DtTypeName(dt) : "var");
wr.WriteLine(" _this = this;");

}
wr.Fork(-1).WriteLine("TAIL_CALL_START: ;");
return wr;
Expand Down Expand Up @@ -2291,8 +2295,7 @@ private string FullTypeName(UserDefinedType udt, MemberDecl/*?*/ member = null,
if ((cl is DatatypeDecl)
&& !ignoreInterface
&& (member is null || !NeedsCustomReceiver(member))) {
var prefix = cl.EnclosingModuleDefinition.IsDefaultModule ? "" : IdProtect(cl.EnclosingModuleDefinition.CompileName) + ".";
return prefix + "_I" + cl.CompileName;
return (cl.EnclosingModuleDefinition.IsDefaultModule ? "" : IdProtect(cl.EnclosingModuleDefinition.CompileName) + ".") + DtTypeName(cl, false);
}

if (cl.EnclosingModuleDefinition.IsDefaultModule) {
Expand Down
12 changes: 12 additions & 0 deletions Test/git-issues/git-issue-2173.dfy
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// RUN: %dafny /compile:4 /compileTarget:cs "%s" >> "%t"

datatype T = Leaf(x: int) | T(t: T) {
function method {:tailrecursion} TR() : int {
if Leaf? then 0
else t.TR()
}
}

method Main() {
print Leaf(0).TR(), '\n';
}
6 changes: 6 additions & 0 deletions Test/git-issues/git-issue-2173.dfy.expect
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

Dafny program verifier finished with 1 verified, 0 errors
Running...

0