Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Fixes to enum semantics:

  Bitwise operators on flags enums now produce an enum instance.
  A | B will always produce the same enum instance (A, B) instead of producing a new one every time. This ensures that all forms of equality tests work.
  Casting a nullable enum to an integer will not convert the null into something else.
Fix an unhandled exception in EvaluatorPool.
  • Loading branch information...
commit 3c5d7c3160903b4c87054441b93843fcdc778cf1 1 parent 83ee171
@kg kg authored
View
12 JSIL/AST/JSExpressionTypes.cs
@@ -1692,7 +1692,7 @@ protected JSAsExpression (JSExpression inner, TypeReference newType)
public static JSExpression New (JSExpression inner, TypeReference newType, TypeSystem typeSystem) {
return NewInternal(
- inner, newType, typeSystem,
+ inner, newType, typeSystem, false,
() => new JSAsExpression(inner, newType)
);
}
@@ -1707,9 +1707,9 @@ protected JSCastExpression (JSExpression inner, TypeReference newType)
NewType = newType;
}
- public static JSExpression New (JSExpression inner, TypeReference newType, TypeSystem typeSystem) {
+ public static JSExpression New (JSExpression inner, TypeReference newType, TypeSystem typeSystem, bool force = false) {
return NewInternal(
- inner, newType, typeSystem,
+ inner, newType, typeSystem, force,
() => new JSCastExpression(inner, newType)
);
}
@@ -1726,14 +1726,14 @@ protected JSCastExpression (JSExpression inner, TypeReference newType)
return false;
}
- internal static JSExpression NewInternal (JSExpression inner, TypeReference newType, TypeSystem typeSystem, Func<JSExpression> make) {
+ internal static JSExpression NewInternal (JSExpression inner, TypeReference newType, TypeSystem typeSystem, bool force, Func<JSExpression> make) {
int rankCurrent, rankNew;
var currentType = inner.GetActualType(typeSystem);
var currentDerefed = TypeUtil.FullyDereferenceType(currentType, out rankCurrent);
var newDerefed = TypeUtil.FullyDereferenceType(newType, out rankNew);
- if (TypeUtil.TypesAreEqual(currentDerefed, newDerefed, false))
+ if (TypeUtil.TypesAreEqual(currentDerefed, newDerefed, false) && !force)
return inner;
if ((rankCurrent == rankNew) && (rankCurrent > 0)) {
@@ -1744,7 +1744,7 @@ protected JSCastExpression (JSExpression inner, TypeReference newType)
}
var newResolved = newDerefed.Resolve();
- if ((newResolved != null) && newResolved.IsInterface) {
+ if (!force && (newResolved != null) && newResolved.IsInterface) {
var currentResolved = currentDerefed.Resolve();
if (currentResolved != null) {
View
9 JSIL/ILBlockTranslator.cs
@@ -277,15 +277,6 @@ IEnumerable<ILVariable> allVariables
)
return new JSUntranslatableExpression(node);
- if (TypeUtil.IsEnum(node.Arguments[0].InferredType ?? node.Arguments[0].ExpectedType)
- || TypeUtil.IsEnum(node.Arguments[1].InferredType ?? node.Arguments[1].ExpectedType)
- ) {
- if (op == JSOperator.Equal)
- op = JSOperator.EqualLoose;
- else if (op == JSOperator.NotEqual)
- op = JSOperator.NotEqualLoose;
- }
-
var resultType = node.InferredType ?? node.ExpectedType;
var result = new JSBinaryOperatorExpression(
op, lhs, rhs, resultType
View
10 JSIL/SpecialIdentifiers.cs
@@ -178,6 +178,16 @@ public class JSILIdentifier : JSIdentifier {
);
}
+ public JSInvocationExpression ValueOfNullable (JSExpression nullableExpression) {
+ var valueType = nullableExpression.GetActualType(TypeSystem);
+ valueType = TypeUtil.StripNullable(valueType);
+
+ return JSInvocationExpression.InvokeStatic(
+ Dot("ValueOfNullable", valueType),
+ new[] { nullableExpression }, true
+ );
+ }
+
public JSInvocationExpression CreateInstanceOfType (TypeReference type) {
return JSInvocationExpression.InvokeStatic(
Dot(new JSFakeMethod("CreateInstanceOfType", type, new[] { TypeSystem.Object }, MethodTypes)),
View
13 JSIL/Transforms/ExpandCastExpressions.cs
@@ -48,6 +48,7 @@ public class ExpandCastExpressions : JSAstVisitor {
IntroduceEnumCasts.IsEnumOrNullableEnum(currentType)
) {
var enumInfo = TypeInfo.Get(currentType);
+ var isNullable = TypeUtil.IsNullable(currentType);
if (targetType.MetadataType == MetadataType.Boolean) {
EnumMemberInfo enumMember;
@@ -68,9 +69,15 @@ public class ExpandCastExpressions : JSAstVisitor {
));
}
} else if (TypeUtil.IsNumeric(targetType)) {
- newExpression = JSInvocationExpression.InvokeMethod(
- JS.valueOf(targetType), ce.Expression, null, true
- );
+ if (isNullable) {
+ newExpression = JSIL.ValueOfNullable(
+ ce.Expression
+ );
+ } else {
+ newExpression = JSInvocationExpression.InvokeMethod(
+ JS.valueOf(targetType), ce.Expression, null, true
+ );
+ }
} else if (targetType.FullName == "System.Enum") {
newExpression = ce.Expression;
} else {
View
43 JSIL/Transforms/IntroduceEnumCasts.cs
@@ -16,6 +16,7 @@ public class IntroduceEnumCasts : JSAstVisitor {
public readonly JSSpecialIdentifiers JS;
private readonly HashSet<JSOperator> LogicalOperators;
+ private readonly HashSet<JSOperator> BitwiseOperators;
public IntroduceEnumCasts (TypeSystem typeSystem, JSSpecialIdentifiers js, TypeInfoProvider typeInfo, MethodTypeFactory methodTypes) {
TypeSystem = typeSystem;
@@ -28,6 +29,12 @@ public class IntroduceEnumCasts : JSAstVisitor {
JSOperator.LogicalOr,
JSOperator.LogicalNot
};
+
+ BitwiseOperators = new HashSet<JSOperator>() {
+ JSOperator.BitwiseAnd,
+ JSOperator.BitwiseOr,
+ JSOperator.BitwiseXor
+ };
}
public static bool IsEnumOrNullableEnum (TypeReference tr) {
@@ -89,21 +96,35 @@ public class IntroduceEnumCasts : JSAstVisitor {
var resultType = boe.GetActualType(TypeSystem);
var resultIsEnum = IsEnumOrNullableEnum(resultType);
- if ((leftIsEnum || rightIsEnum) && LogicalOperators.Contains(boe.Operator)) {
- if (leftIsEnum) {
- var cast = JSInvocationExpression.InvokeMethod(
- JS.valueOf(TypeSystem.Int32), boe.Left, null, true
- );
+ var eitherIsEnum = leftIsEnum || rightIsEnum;
- boe.ReplaceChild(boe.Left, cast);
- }
+ if (LogicalOperators.Contains(boe.Operator)) {
+ if (eitherIsEnum) {
+ if (leftIsEnum) {
+ var cast = JSInvocationExpression.InvokeMethod(
+ JS.valueOf(TypeSystem.Int32), boe.Left, null, true
+ );
- if (rightIsEnum) {
- var cast = JSInvocationExpression.InvokeMethod(
- JS.valueOf(TypeSystem.Int32), boe.Right, null, true
+ boe.ReplaceChild(boe.Left, cast);
+ }
+
+ if (rightIsEnum) {
+ var cast = JSInvocationExpression.InvokeMethod(
+ JS.valueOf(TypeSystem.Int32), boe.Right, null, true
+ );
+
+ boe.ReplaceChild(boe.Right, cast);
+ }
+ }
+ } else if (BitwiseOperators.Contains(boe.Operator)) {
+ var parentCast = ParentNode as JSCastExpression;
+ if (resultIsEnum && ((parentCast == null) || (parentCast.NewType != resultType))) {
+ var cast = JSCastExpression.New(
+ boe, resultType, TypeSystem, true
);
- boe.ReplaceChild(boe.Right, cast);
+ ParentNode.ReplaceChild(boe, cast);
+ VisitReplacement(cast);
}
}
View
17 JSIL/TypeUtil.cs
@@ -97,11 +97,24 @@ public static class TypeUtil {
}
}
+ public static bool IsNullable (TypeReference type) {
+ int temp;
+ type = FullyDereferenceType(type, out temp);
+
+ var git = type as GenericInstanceType;
+ if ((git != null) && (git.Name == "Nullable`1"))
+ return true;
+
+ return false;
+ }
+
public static TypeReference StripNullable (TypeReference type) {
+ int temp;
+ type = FullyDereferenceType(type, out temp);
+
var git = type as GenericInstanceType;
- if ((git != null) && (git.Name == "Nullable`1")) {
+ if ((git != null) && (git.Name == "Nullable`1"))
return git.GenericArguments[0];
- }
return type;
}
View
32 Libraries/JSIL.Core.js
@@ -3671,12 +3671,11 @@ JSIL.EnumValue.prototype.toString = function () {
var name = names[i];
var nameValue = enumType[name].value;
- if (nameValue) {
+ if (nameValue === this.value) {
+ result.push(name);
+ } else if (nameValue) {
if ((this.value & nameValue) === nameValue)
result.push(name);
- } else {
- if (!this.value)
- result.push(name);
}
}
@@ -3786,9 +3785,21 @@ JSIL.MakeEnum = function (fullName, isPublic, members, isFlagsEnum) {
valueProto, "__ThisTypeId__", result.__TypeId__
);
+ // Because there's no way to change the behavior of ==,
+ // we need to ensure that all calls to $MakeValue for a given value
+ // return the same instance.
+ // FIXME: Memory leak! Weak references would help here, but TC39 apparently thinks
+ // hiding GC behavior from developers is more important than letting them control
+ // memory usage.
+ var valueCache = {};
+
result.$MakeValue = function (value, name) {
- // TODO: Cache value instances to reduce garbage creation?
- return new valueType(value, name);
+ var result = valueCache[value];
+
+ if (!result)
+ result = valueCache[value] = new valueType(value, name);
+
+ return result;
};
return result;
@@ -6437,4 +6448,11 @@ JSIL.ImplementExternals("System.Enum", function ($) {
return enumType[enumType.__ValueToName__[value]];
}
);
-});
+});
+
+JSIL.ValueOfNullable = function (value) {
+ if (value === null)
+ return value;
+ else
+ return value.valueOf();
+};
View
2  Libraries/JSIL.XNACore.js
@@ -5058,6 +5058,8 @@ JSIL.ImplementExternals("Microsoft.Xna.Framework.Graphics.SpriteBatch", function
}
if (effects) {
+ effects = effects.valueOf();
+
if (effects & this.spriteEffects.FlipHorizontally) {
if (!needRestore)
this.$save();
View
1  Tests/ComparisonTests.cs
@@ -239,6 +239,7 @@ public class ComparisonTests : GenericTestFixture {
@"TestCases\EnumCheckType.cs",
@"TestCases\EnumNullableArithmetic.cs",
@"TestCases\EnumAnonymousMethod.cs",
+ @"TestCases\CompareFlagsEnums.cs",
}, MakeDefaultProvider(), new AssemblyCache()
);
}
View
10 Tests/EvaluatorPool.cs
@@ -130,11 +130,17 @@ public class Evaluator : IDisposable {
Process = Process.Start(psi);
ThreadPool.QueueUserWorkItem((_) => {
- _StdOut = Process.StandardOutput.ReadToEnd();
+ try {
+ _StdOut = Process.StandardOutput.ReadToEnd();
+ } catch {
+ }
stdoutSignal.Set();
});
ThreadPool.QueueUserWorkItem((_) => {
- _StdErr = Process.StandardError.ReadToEnd();
+ try {
+ _StdErr = Process.StandardError.ReadToEnd();
+ } catch {
+ }
stderrSignal.Set();
});
View
29 Tests/FailingTestCases/FlagsEnumOperators.cs
@@ -1,29 +0,0 @@
-using System;
-
-public static class Program {
- [Flags]
- public enum FlagsEnum {
- A = 1,
- B = 2,
- C = 4
- }
-
- public static void Main (string[] args) {
- var a = FlagsEnum.A;
-
- Console.WriteLine(
- "'{0}' '{1}' '{2}' '{3}'",
- a,
- a | FlagsEnum.A,
- a | FlagsEnum.B,
- a | FlagsEnum.B | FlagsEnum.C
- );
-
- Console.WriteLine(
- "'{0}' '{1}' '{2}'",
- a & FlagsEnum.A,
- a & FlagsEnum.B,
- a & (FlagsEnum.A | FlagsEnum.B)
- );
- }
-}
View
2  Tests/FormattingTests.cs
@@ -326,7 +326,7 @@ public class FormattingTests : GenericTestFixture {
public void FlagsEnumsWithZeroValues () {
var generatedJs = GetJavascript(
@"SpecialTestCases\FlagsEnumsWithZeroValues.cs",
- "B A\r\nB 0"
+ "B A\r\nB A"
);
try {
Assert.IsFalse(generatedJs.Contains("| $asm01.Program.SimpleEnum.E"));
View
12 Tests/SpecialTestCases/FlagsEnumsWithZeroValues.cs
@@ -10,11 +10,15 @@ public enum SimpleEnum {
E = 0
}
+ public static SimpleEnum ReturnEnum (SimpleEnum value) {
+ return value;
+ }
+
public static void Main (string[] args) {
- const SimpleEnum a = SimpleEnum.B;
- SimpleEnum b = SimpleEnum.E;
+ const SimpleEnum b = SimpleEnum.B;
+ SimpleEnum e = SimpleEnum.E;
- Console.WriteLine("{0} {1}", a, b);
- Console.WriteLine("{0} {1}", a & SimpleEnum.B, b & SimpleEnum.D);
+ Console.WriteLine("{0} {1}", b, e);
+ Console.WriteLine("{0} {1}", b & ReturnEnum(SimpleEnum.B), e & ReturnEnum(SimpleEnum.D));
}
}
View
26 Tests/TestCases/CompareFlagsEnums.cs
@@ -0,0 +1,26 @@
+using System;
+
+public static class Program {
+ [Flags]
+ public enum SimpleEnum {
+ A = 1,
+ B = 2,
+ C = 4
+ }
+
+ public static SimpleEnum Or (SimpleEnum lhs, SimpleEnum rhs) {
+ return lhs | rhs;
+ }
+
+ public static void Main (string[] args) {
+ var a = SimpleEnum.A;
+ var b = SimpleEnum.B;
+
+ var c = Or(a, b);
+ var d = Or(b, a);
+
+ Console.WriteLine("{0} {1} {2} {3}", a, b, c, d);
+ Console.WriteLine("{0}", c == a ? "true" : "false");
+ Console.WriteLine("{0}", c == d ? "true" : "false");
+ }
+}
View
2  Tests/Tests.csproj
@@ -103,7 +103,6 @@
<None Include="TestCases\GenericStaticConstructorOrdering2.cs" />
<None Include="TestCases\StaticInitializersInGenericTypesSettingStaticFields2.cs" />
<None Include="TestCases\EnumCheckType.cs" />
- <None Include="FailingTestCases\FlagsEnumOperators.cs" />
<None Include="TestCases\NullableArithmetic.cs" />
<None Include="TestCases\NullableComparison.cs" />
<None Include="TestCases\NullableComparisonWithCast.cs" />
@@ -186,6 +185,7 @@
<None Include="XMLTestCases\WriteStartDocument.cs" />
<None Include="XMLTestCases\WriteEndDocument.cs" />
<None Include="SimpleTestCases\IncrementBaseProperty.cs" />
+ <None Include="TestCases\CompareFlagsEnums.cs" />
<Compile Include="XMLTests.cs" />
<Compile Include="DependencyTests.cs" />
<None Include="TestCases\GenericParameterNameShadowing.cs" />
Please sign in to comment.
Something went wrong with that request. Please try again.