Skip to content

Commit 7e81f44

Browse files
authored
feat: Add bounded polymorphism (#5547)
# Description This PR adds bounded polymorphism to the Dafny language. This feature allows any formal type parameter to be declared with a suffix of any number of ``` extends Type ``` clauses, where `Type` is any trait or subset type thereof. At call sites, the actual type arguments are checked to be subtypes of the listed type bounds. Where the formal type parameters are in scope, an expression whose type is the type parameter can be converted to a supertype of any of the type bounds by an explicit type conversion (`as`). In some cases, the type conversion is inferred automatically and can then be omitted from the program text. The feature requires `--type-system-refresh` for full functionality. Closes #5211 # Example For `test.dfy` being the program ``` dafny trait Printable { method Print() } method SelectPrintReturn<T extends Printable>(operateOnLeft: bool, left: T, right: T) returns (r: T) { r := if operateOnLeft then left else right; (r as Printable).Print(); } datatype Dt extends Printable = Dt(u: int) { method Print() { print u, "\n"; } } method Main() { var d0 := Dt(500); var d1 := Dt(702); var e0 := SelectPrintReturn(true, d0, d1); // 500 var e1 := SelectPrintReturn(false, d0, d1); // 702 print (d0, d1) == (d0, d1), "\n"; // true } ``` we get: ``` > dafny run test.dfy --type-system-refresh --general-traits=datatype Dafny program verifier finished with 3 verified, 0 errors 500 702 true ``` # Bonus The PR also * suppresses near-identical error messages (in particular, it reports only one error for conditions that split into several checks, like the type equality `A==B` which turns into both `A :> B` and `B >: A`) <small>By submitting this pull request, I confirm that my contribution is made under the terms of the [MIT license](https://github.com/dafny-lang/dafny/blob/master/LICENSE.txt).</small>
1 parent 8dedc64 commit 7e81f44

File tree

58 files changed

+1854
-187
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+1854
-187
lines changed

Source/DafnyCore/AST/Cloner.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ public DatatypeCtor CloneCtor(DatatypeCtor ct) {
194194
public TypeParameter CloneTypeParam(TypeParameter tp) {
195195
return (TypeParameter)typeParameterClones.GetOrCreate(tp,
196196
() => new TypeParameter(Range(tp.RangeToken), tp.NameNode.Clone(this), tp.VarianceSyntax,
197-
CloneTPChar(tp.Characteristics)));
197+
CloneTPChar(tp.Characteristics), tp.TypeBounds.ConvertAll(CloneType)));
198198
}
199199

200200
public virtual MemberDecl CloneMember(MemberDecl member, bool isReference) {

Source/DafnyCore/AST/Expressions/Applications/MemberSelectExpr.cs

+1-2
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,6 @@ public MemberSelectExpr(IToken tok, Expression obj, Field field)
238238
var receiverType = obj.Type.NormalizeExpand();
239239
this.TypeApplication_AtEnclosingClass = receiverType.TypeArgs;
240240
this.TypeApplication_JustMember = new List<Type>();
241-
this.ResolvedOutparameterTypes = new List<Type>();
242241

243242
var typeMap = new Dictionary<TypeParameter, Type>();
244243
if (receiverType is UserDefinedType udt) {
@@ -299,4 +298,4 @@ public IEnumerable<IHasNavigationToken> GetReferences() {
299298
}
300299

301300
public IToken NavigationToken => tok;
302-
}
301+
}

Source/DafnyCore/AST/Grammar/Printer/Printer.cs

+7-3
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ public void PrintTopLevelDecls(CompilationData compilation, IEnumerable<TopLevel
290290
var dd = (NewtypeDecl)d;
291291
if (i++ != 0) { wr.WriteLine(); }
292292
Indent(indent);
293-
PrintClassMethodHelper("newtype", dd.Attributes, dd.Name, new List<TypeParameter>());
293+
PrintClassMethodHelper("newtype", dd.Attributes, dd.Name, dd.TypeArgs);
294294
PrintExtendsClause(dd);
295295
wr.Write(" = ");
296296
if (dd.Var == null) {
@@ -663,7 +663,7 @@ public void PrintClass(ClassLikeDecl c, int indent, string fileBeingPrinted) {
663663
Contract.Requires(c != null);
664664

665665
Indent(indent);
666-
PrintClassMethodHelper((c is TraitDecl) ? "trait" : "class", c.Attributes, c.Name, c.TypeArgs);
666+
PrintClassMethodHelper(c is TraitDecl ? "trait" : "class", c.Attributes, c.Name, c.TypeArgs);
667667
if (c.IsRefining) {
668668
wr.Write(" ...");
669669
} else {
@@ -795,7 +795,11 @@ public string TypeParamString(TypeParameter tp) {
795795
Contract.Assert(false); // unexpected VarianceSyntax
796796
throw new cce.UnreachableException();
797797
}
798-
return variance + tp.Name + TPCharacteristicsSuffix(tp.Characteristics);
798+
var paramString = variance + tp.Name + TPCharacteristicsSuffix(tp.Characteristics);
799+
foreach (var typeBound in tp.TypeBounds) {
800+
paramString += $" extends {typeBound.TypeName(options, null, true)}";
801+
}
802+
return paramString;
799803
}
800804

801805
private void PrintArrowType(string arrow, string internalName, List<TypeParameter> typeArgs) {

Source/DafnyCore/AST/TypeDeclarations/NonNullTypeDecl.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public class NonNullTypeDecl : SubsetTypeDecl {
1313
/// in order to build values that depend on previously computed parameters.
1414
/// </summary>
1515
public NonNullTypeDecl(ClassLikeDecl cl)
16-
: this(cl, cl.TypeArgs.ConvertAll(tp => new TypeParameter(tp.RangeToken, tp.NameNode, tp.VarianceSyntax, tp.Characteristics))) {
16+
: this(cl, TypeParameter.CloneTypeParameters(cl.TypeArgs)) {
1717
Contract.Requires(cl != null);
1818
}
1919

@@ -34,10 +34,10 @@ private NonNullTypeDecl(ClassLikeDecl cl, List<TypeParameter> tps, BoundVar id)
3434
Class = cl;
3535
}
3636

37-
public override List<Type> ParentTypes(List<Type> typeArgs) {
38-
List<Type> result = new List<Type>(base.ParentTypes(typeArgs));
37+
public override List<Type> ParentTypes(List<Type> typeArgs, bool includeTypeBounds) {
38+
List<Type> result = new List<Type>(base.ParentTypes(typeArgs, includeTypeBounds));
3939

40-
foreach (var rhsParentType in Class.ParentTypes(typeArgs)) {
40+
foreach (var rhsParentType in Class.ParentTypes(typeArgs, includeTypeBounds)) {
4141
var rhsParentUdt = (UserDefinedType)rhsParentType; // all parent types of .Class are expected to be possibly-null class types
4242
Contract.Assert(rhsParentUdt.ResolvedClass is TraitDecl);
4343
result.Add(UserDefinedType.CreateNonNullTypeIfReferenceType(rhsParentUdt));

Source/DafnyCore/AST/TypeDeclarations/SubsetTypeDecl.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public SubsetTypeDecl(RangeToken rangeToken, Name name, TypeParameter.TypeParame
5454
WKind RedirectingTypeDecl.WitnessKind => WitnessKind;
5555
Expression RedirectingTypeDecl.Witness => Witness;
5656

57-
public override List<Type> ParentTypes(List<Type> typeArgs) {
57+
public override List<Type> ParentTypes(List<Type> typeArgs, bool includeTypeBounds) {
5858
return new List<Type> { RhsWithArgument(typeArgs) };
5959
}
6060
public bool ShouldVerify => true; // This could be made more accurate

Source/DafnyCore/AST/TypeDeclarations/TopLevelDecl.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,10 @@ public TopLevelDecl ViewAsClass {
9898
/// class C<X> extends J<X, int>
9999
/// C.ParentTypes(real) = J<real, int> // non-null types C and J
100100
/// C?.ParentTypes(real) = J?<real, int> // possibly-null type C? and J?
101+
///
102+
/// If "includeTypeBounds" is "true", then for a type parameter, ParentTypes() returns the type bounds.
101103
/// </summary>
102-
public virtual List<Type> ParentTypes(List<Type> typeArgs) {
104+
public virtual List<Type> ParentTypes(List<Type> typeArgs, bool includeTypeBounds) {
103105
Contract.Requires(typeArgs != null);
104106
Contract.Requires(this.TypeArgs.Count == typeArgs.Count);
105107
return new List<Type>();

Source/DafnyCore/AST/TypeDeclarations/TopLevelDeclWithMembers.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ public void SetMembersBeforeResolution() {
104104
MembersBeforeResolution = Members.ToImmutableList();
105105
}
106106

107-
public List<Type> RawTraitsWithArgument(List<Type> typeArgs) {
107+
private List<Type> RawTraitsWithArgument(List<Type> typeArgs) {
108108
Contract.Requires(typeArgs != null);
109109
Contract.Requires(typeArgs.Count == TypeArgs.Count);
110110
// Instantiate with the actual type arguments
@@ -115,7 +115,7 @@ public List<Type> RawTraitsWithArgument(List<Type> typeArgs) {
115115
});
116116
}
117117

118-
public override List<Type> ParentTypes(List<Type> typeArgs) {
118+
public override List<Type> ParentTypes(List<Type> typeArgs, bool includeTypeBounds) {
119119
return RawTraitsWithArgument(typeArgs);
120120
}
121121

Source/DafnyCore/AST/Types/TypeParameter.cs

+30-2
Original file line numberDiff line numberDiff line change
@@ -171,16 +171,30 @@ public bool SupportsEquality {
171171
}
172172
public int PositionalIndex; // which type parameter this is (ie. in C<S, T, U>, S is 0, T is 1 and U is 2).
173173

174-
public TypeParameter(RangeToken rangeToken, Name name, TPVarianceSyntax varianceS, TypeParameterCharacteristics characteristics)
174+
public readonly List<Type> TypeBounds;
175+
176+
public IEnumerable<TopLevelDecl> TypeBoundHeads {
177+
get {
178+
foreach (var typeBound in TypeBounds) {
179+
if (typeBound is UserDefinedType { ResolvedClass: { } parentDecl }) {
180+
yield return parentDecl;
181+
}
182+
}
183+
}
184+
}
185+
186+
public TypeParameter(RangeToken rangeToken, Name name, TPVarianceSyntax varianceS, TypeParameterCharacteristics characteristics,
187+
List<Type> typeBounds)
175188
: base(rangeToken, name, null, new List<TypeParameter>(), null, false) {
176189
Contract.Requires(rangeToken != null);
177190
Contract.Requires(name != null);
178191
Characteristics = characteristics;
179192
VarianceSyntax = varianceS;
193+
TypeBounds = typeBounds;
180194
}
181195

182196
public TypeParameter(RangeToken rangeToken, Name name, TPVarianceSyntax varianceS)
183-
: this(rangeToken, name, varianceS, new TypeParameterCharacteristics(false)) {
197+
: this(rangeToken, name, varianceS, new TypeParameterCharacteristics(false), new List<Type>()) {
184198
Contract.Requires(rangeToken != null);
185199
Contract.Requires(name != null);
186200
}
@@ -191,6 +205,17 @@ public TypeParameter(RangeToken tok, Name name, int positionalIndex, ParentType
191205
Parent = parent;
192206
}
193207

208+
/// <summary>
209+
/// Return a list of unresolved clones of the type parameters in "typeParameters".
210+
/// </summary>
211+
public static List<TypeParameter> CloneTypeParameters(List<TypeParameter> typeParameters) {
212+
var cloner = new Cloner();
213+
return typeParameters.ConvertAll(tp => {
214+
var typeBounds = tp.TypeBounds.ConvertAll(cloner.CloneType);
215+
return new TypeParameter(tp.RangeToken, tp.NameNode, tp.VarianceSyntax, tp.Characteristics, typeBounds);
216+
});
217+
}
218+
194219
public override string FullName {
195220
get {
196221
// when debugging, print it all:
@@ -231,4 +256,7 @@ public static Dictionary<TypeParameter, Type> SubstitutionMap(List<TypeParameter
231256
return subst;
232257
}
233258

259+
public override List<Type> ParentTypes(List<Type> typeArgs, bool includeTypeBounds) {
260+
return includeTypeBounds ? TypeBounds : new List<Type>();
261+
}
234262
}

Source/DafnyCore/AST/Types/Types.cs

+8-2
Original file line numberDiff line numberDiff line change
@@ -1665,7 +1665,7 @@ public bool ContainsProxy(TypeProxy proxy) {
16651665
}
16661666
}
16671667

1668-
public virtual List<Type> ParentTypes() {
1668+
public virtual List<Type> ParentTypes(bool includeTypeBounds) {
16691669
return new List<Type>();
16701670
}
16711671

@@ -1692,7 +1692,13 @@ public virtual bool IsSubtypeOf(Type super, bool ignoreTypeArguments, bool ignor
16921692
return ignoreTypeArguments || CompatibleTypeArgs(super, sub);
16931693
}
16941694

1695-
return sub.ParentTypes().Any(parentType => parentType.IsSubtypeOf(super, ignoreTypeArguments, ignoreNullity));
1695+
// There is a special case, namely when super is the non-null "object". Since "sub.ParentTypes()" only gives
1696+
// back the explicitly declared parent traits, the general case below may miss it.
1697+
if (super.IsObject) {
1698+
return sub.IsNonNullRefType;
1699+
}
1700+
1701+
return sub.ParentTypes(true).Any(parentType => parentType.IsSubtypeOf(super, ignoreTypeArguments, ignoreNullity));
16961702
}
16971703

16981704
public static bool CompatibleTypeArgs(Type super, Type sub) {

Source/DafnyCore/AST/Types/UserDefinedType.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -503,8 +503,8 @@ public override bool ComputeMayInvolveReferences(ISet<DatatypeDecl> visitedDatat
503503
return true;
504504
}
505505

506-
public override List<Type> ParentTypes() {
507-
return ResolvedClass != null ? ResolvedClass.ParentTypes(TypeArgs) : base.ParentTypes();
506+
public override List<Type> ParentTypes(bool includeTypeBounds) {
507+
return ResolvedClass != null ? ResolvedClass.ParentTypes(TypeArgs, includeTypeBounds) : base.ParentTypes(includeTypeBounds);
508508
}
509509

510510
public override bool IsSubtypeOf(Type super, bool ignoreTypeArguments, bool ignoreNullity) {

Source/DafnyCore/Backends/CSharp/CsharpCodeGenerator.cs

+13-5
Original file line numberDiff line numberDiff line change
@@ -2748,18 +2748,24 @@ protected override void EmitIndexCollectionSelect(Expression source, Expression
27482748

27492749
protected override void EmitIndexCollectionUpdate(Expression source, Expression index, Expression value,
27502750
CollectionType resultCollectionType, bool inLetExprBody, ConcreteSyntaxTree wr, ConcreteSyntaxTree wStmts) {
2751-
if (resultCollectionType is SeqType or MapType) {
2751+
if (resultCollectionType is SeqType) {
27522752
wr.Write(TypeHelperName(resultCollectionType, wr, source.tok) + ".Update");
27532753
wr.Append(ParensList(
27542754
Expr(source, inLetExprBody, wStmts),
27552755
Expr(index, inLetExprBody, wStmts),
27562756
CoercedExpr(value, resultCollectionType.ValueArg, inLetExprBody, wStmts)));
2757+
} else if (resultCollectionType is MapType resultMapType) {
2758+
wr.Write(TypeHelperName(resultCollectionType, wr, source.tok) + ".Update");
2759+
wr.Append(ParensList(
2760+
Expr(source, inLetExprBody, wStmts),
2761+
CoercedExpr(index, resultMapType.Domain, inLetExprBody, wStmts),
2762+
CoercedExpr(value, resultMapType.Range, inLetExprBody, wStmts)));
27572763
} else {
27582764
TrParenExpr(source, wr, inLetExprBody, wStmts);
27592765
wr.Write(".Update");
27602766
wr.Append(ParensList(
2761-
Expr(index, inLetExprBody, wStmts),
2762-
CoercedExpr(value, resultCollectionType.ValueArg, inLetExprBody, wStmts)));
2767+
CoercedExpr(index, resultCollectionType.ValueArg, inLetExprBody, wStmts),
2768+
Expr(value, inLetExprBody, wStmts)));
27632769
}
27642770
}
27652771

@@ -2890,12 +2896,14 @@ protected override ConcreteSyntaxTree ToFatPointer(Type type, ConcreteSyntaxTree
28902896
protected override ConcreteSyntaxTree EmitDowncast(Type from, Type to, IToken tok, ConcreteSyntaxTree wr) {
28912897
from = from.NormalizeExpand();
28922898
to = to.NormalizeExpand();
2893-
Contract.Assert(Options.Get(CommonOptionBag.GeneralTraits) != CommonOptionBag.GeneralTraitsOptions.Legacy || from.IsRefType == to.IsRefType);
2899+
Contract.Assert(Options.Get(CommonOptionBag.GeneralTraits) != CommonOptionBag.GeneralTraitsOptions.Legacy ||
2900+
from.IsRefType == to.IsRefType ||
2901+
(from.IsTypeParameter && to.IsTraitType));
28942902

28952903
var w = new ConcreteSyntaxTree();
28962904
if (from.IsTraitType && to.AsNewtype != null) {
28972905
wr.Format($"(({to.AsNewtype.GetFullCompileName(Options)})({w}))");
2898-
} else if (to.IsRefType || to.IsTraitType || from.IsTraitType) {
2906+
} else if (to.IsRefType || to.IsTraitType || from.IsTraitType || to.IsTypeParameter) {
28992907
wr.Format($"(({TypeName(to, wr, tok)})({w}))");
29002908
} else {
29012909
Contract.Assert(Type.SameHead(from, to));

Source/DafnyCore/Backends/Dafny/DafnyCodeGenerator.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ protected override ConcreteSyntaxTree EmitCoercionIfNecessary(Type from, Type to
168168
to = toNonNull.RhsWithArgument(toUdt.TypeArgs);
169169
}
170170

171-
return EmitCoercionIfNecessary(from, to, tok, new BuilderSyntaxTree<ExprContainer>(nullConvert, this));
171+
return base.EmitCoercionIfNecessary(from, to, tok, new BuilderSyntaxTree<ExprContainer>(nullConvert, this));
172172
} else {
173173
return base.EmitCoercionIfNecessary(from, to, tok, wr);
174174
}

Source/DafnyCore/Backends/GoLang/GoCodeGenerator.cs

+15-1
Original file line numberDiff line numberDiff line change
@@ -2488,6 +2488,10 @@ private string IdName(Declaration decl) {
24882488

24892489
protected override string PrefixForForcedCapitalization => "Go_";
24902490

2491+
public override Type ResultTypeAsViewedByFunctionBody(Function f) {
2492+
return f.Original.ResultType;
2493+
}
2494+
24912495
protected override string IdMemberName(MemberSelectExpr mse) {
24922496
return Capitalize(mse.MemberName);
24932497
}
@@ -3899,7 +3903,7 @@ protected override ConcreteSyntaxTree EmitCoercionIfNecessary(Type/*?*/ from, Ty
38993903
} else if (to.IsTypeParameter || (from != null && EqualsUpToParameters(from, to))) {
39003904
// do nothing
39013905
return wr;
3902-
} else if (from != null && from.IsSubtypeOf(to, true, true)) {
3906+
} else if (from != null && !from.IsTypeParameter && from.IsSubtypeOf(to, true, true)) {
39033907
// upcast
39043908
return wr;
39053909
} else if (from == null || from.IsTypeParameter || to.IsSubtypeOf(from, true, true)) {
@@ -3934,6 +3938,16 @@ protected override ConcreteSyntaxTree EmitCoercionIfNecessary(Type/*?*/ from, Ty
39343938
}
39353939
}
39363940

3941+
protected override ConcreteSyntaxTree EmitDowncast(Type from, Type to, IToken tok, ConcreteSyntaxTree wr) {
3942+
if (to.IsTraitType) {
3943+
wr.Write("{0}.CastTo_(", TypeName_Companion(to.AsTraitType, wr, tok));
3944+
var w = wr.Fork();
3945+
wr.Write(")");
3946+
return w;
3947+
}
3948+
return base.EmitDowncast(from, to, tok, wr);
3949+
}
3950+
39373951
protected override ConcreteSyntaxTree EmitCoercionToNativeInt(ConcreteSyntaxTree wr) {
39383952
var w = wr.Fork();
39393953
wr.Write(".(int)");

Source/DafnyCore/Backends/Java/JavaCodeGenerator.cs

+16-7
Original file line numberDiff line numberDiff line change
@@ -1697,11 +1697,10 @@ protected override void EmitIndexCollectionSelect(Expression source, Expression
16971697
ConcreteSyntaxTree wr, ConcreteSyntaxTree wStmts) {
16981698
// Taken from C# compiler, assuming source is a DafnySequence type.
16991699
if (source.Type.NormalizeToAncestorType().AsMultiSetType is { } multiSetType) {
1700-
wr = EmitCoercionIfNecessary(from: NativeObjectType, to: Type.Int, tok: source.tok, wr: wr);
17011700
wr.Write($"{DafnyMultiSetClass}.<{BoxedTypeName(multiSetType.Arg, wr, Token.NoToken)}>multiplicity(");
17021701
TrParenExpr(source, wr, inLetExprBody, wStmts);
17031702
wr.Write(", ");
1704-
wr.Append(Expr(index, inLetExprBody, wStmts));
1703+
wr.Append(JavaCoercedExpr(index, multiSetType.Arg, inLetExprBody, wStmts));
17051704
wr.Write(")");
17061705
} else if (source.Type.NormalizeToAncestorType().AsMapType is { } mapType) {
17071706
wr = EmitCoercionIfNecessary(from: NativeObjectType, to: mapType.Range, tok: source.tok, wr: wr);
@@ -1731,21 +1730,31 @@ protected override void EmitIndexCollectionUpdate(Expression source, Expression
17311730
wr.Append(Expr(source, inLetExprBody, wStmts));
17321731
wr.Write(", ");
17331732
TrExprAsInt(index, wr, inLetExprBody, wStmts);
1733+
wr.Write(", ");
1734+
wr.Append(JavaCoercedExpr(value, resultCollectionType.ValueArg, inLetExprBody, wStmts));
1735+
wr.Write(")");
17341736
} else if (resultCollectionType.AsMapType is { } mapType) {
17351737
wr.Write($"{DafnyMapClass}.<{BoxedTypeName(mapType.Domain, wr, Token.NoToken)}, {BoxedTypeName(mapType.Range, wr, Token.NoToken)}>update(");
17361738
wr.Append(Expr(source, inLetExprBody, wStmts));
17371739
wr.Write(", ");
1738-
wr.Append(Expr(index, inLetExprBody, wStmts));
1740+
wr.Append(JavaCoercedExpr(index, mapType.Domain, inLetExprBody, wStmts));
1741+
wr.Write(", ");
1742+
wr.Append(JavaCoercedExpr(value, mapType.Range, inLetExprBody, wStmts));
1743+
wr.Write(")");
17391744
} else {
17401745
Contract.Assert(resultCollectionType.AsMultiSetType != null);
17411746
wr.Write($"{DafnyMultiSetClass}.<{BoxedTypeName(resultCollectionType.Arg, wr, Token.NoToken)}>update(");
17421747
wr.Append(Expr(source, inLetExprBody, wStmts));
17431748
wr.Write(", ");
1744-
wr.Append(Expr(index, inLetExprBody, wStmts));
1749+
wr.Append(JavaCoercedExpr(index, resultCollectionType.ValueArg, inLetExprBody, wStmts));
1750+
wr.Write(", ");
1751+
wr.Append(Expr(value, inLetExprBody, wStmts));
1752+
wr.Write(")");
17451753
}
1746-
wr.Write(", ");
1747-
wr.Append(CoercedExpr(value, NativeObjectType, inLetExprBody, wStmts));
1748-
wr.Write(")");
1754+
}
1755+
1756+
private ConcreteSyntaxTree JavaCoercedExpr(Expression expr, Type toType, bool inLetExprBody, ConcreteSyntaxTree wStmts) {
1757+
return CoercedExpr(expr, expr.Type.IsTypeParameter ? toType : NativeObjectType, inLetExprBody, wStmts);
17491758
}
17501759

17511760
protected override void EmitRotate(Expression e0, Expression e1, bool isRotateLeft, ConcreteSyntaxTree wr,

Source/DafnyCore/Backends/SinglePassCodeGenerator/SinglePassCodeGenerator.Expression.cs

+6-1
Original file line numberDiff line numberDiff line change
@@ -290,11 +290,16 @@ void EmitExpr(Expression e2, ConcreteSyntaxTree wr2, bool inLetExpr, ConcreteSyn
290290
var fromType = e.E.Type.GetRuntimeType();
291291
var toType = e.ToType.GetRuntimeType();
292292
Contract.Assert(Options.Get(CommonOptionBag.GeneralTraits) != CommonOptionBag.GeneralTraitsOptions.Legacy ||
293-
toType.IsRefType == fromType.IsRefType);
293+
toType.IsRefType == fromType.IsRefType ||
294+
(fromType.IsTypeParameter && toType.IsTraitType));
294295
if (toType.IsRefType || toType.IsTraitType || fromType.IsTraitType) {
295296
var w = EmitCoercionIfNecessary(e.E.Type, e.ToType, e.tok, wr);
296297
w = EmitDowncastIfNecessary(e.E.Type, e.ToType, e.tok, w);
297298
EmitExpr(e.E, inLetExprBody, w, wStmts);
299+
} else if (e.E.Type.IsSubtypeOf(e.ToType, false, false)) {
300+
// conversion is a no-op -- almost, because it may need a cast to deal with bounded type parameters
301+
var w = EmitDowncastIfNecessary(e.E.Type, e.ToType, e.tok, wr);
302+
EmitExpr(e.E, inLetExprBody, w, wStmts);
298303
} else {
299304
EmitConversionExpr(e.E, fromType, toType, inLetExprBody, wr, wStmts);
300305
}

0 commit comments

Comments
 (0)