Permalink
Browse files

Make CSharpAstResolver and Conversions thread-safe.

  • Loading branch information...
dgrunwald committed Mar 11, 2012
1 parent cf331bb commit 3df0cd3946e8e1f443c3db917886f748e704706c
@@ -45,7 +45,7 @@ public static CSharpOperators Get(ICompilation compilation)
CacheManager cache = compilation.CacheManager;
CSharpOperators operators = (CSharpOperators)cache.GetShared(typeof(CSharpOperators));
if (operators == null) {
- operators = (CSharpOperators)cache.GetOrAddShared(typeof(Conversions), new CSharpOperators(compilation));
+ operators = (CSharpOperators)cache.GetOrAddShared(typeof(CSharpOperators), new CSharpOperators(compilation));
}
return operators;
}
@@ -36,8 +36,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
/// Contains the main resolver logic.
/// </summary>
/// <remarks>
- /// Despite being immutable, this class is not thread-safe. (due to caches)
- /// TODO: fix this, it's very hard for NR users to tell whether two CSharpResolvers share the same caches
+ /// This class is thread-safe.
/// </remarks>
public class CSharpResolver
{
@@ -1356,14 +1355,16 @@ public ResolveResult LookupSimpleNameOrTypeName(string identifier, IList<IType>
break;
}
if (cache != null) {
- foundInCache = cache.TryGetValue(identifier, out r);
+ lock (cache)
+ foundInCache = cache.TryGetValue(identifier, out r);
}
}
if (!foundInCache) {
r = LookInCurrentType(identifier, typeArguments, lookupMode, parameterizeResultType);
if (cache != null) {
// also cache missing members (r==null)
- cache[identifier] = r;
+ lock (cache)
+ cache[identifier] = r;
}
}
if (r != null)
@@ -32,14 +32,13 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
/// Contains logic that determines whether an implicit conversion exists between two types.
/// </summary>
/// <remarks>
- /// Because this class internally uses a cache, it is NOT thread-safe!
+ /// This class is thread-safe.
/// </remarks>
public sealed class Conversions
{
- readonly Dictionary<TypePair, Conversion> implicitConversionCache = new Dictionary<TypePair, Conversion>();
+ readonly ConcurrentDictionary<TypePair, Conversion> implicitConversionCache = new ConcurrentDictionary<TypePair, Conversion>();
readonly ICompilation compilation;
readonly IType objectType;
- int subtypeCheckNestingDepth;
public Conversions(ICompilation compilation)
{
@@ -52,11 +51,18 @@ public Conversions(ICompilation compilation)
/// <summary>
/// Gets the Conversions instance for the specified <see cref="ICompilation"/>.
- /// This will make use of the context's cache manager (if available) to reuse the Conversions instance.
+ /// This will make use of the context's cache manager to reuse the Conversions instance.
/// </summary>
public static Conversions Get(ICompilation compilation)
{
- return new Conversions(compilation);
+ if (compilation == null)
+ throw new ArgumentNullException("compilation");
+ CacheManager cache = compilation.CacheManager;
+ Conversions operators = (Conversions)cache.GetShared(typeof(Conversions));
+ if (operators == null) {
+ operators = (Conversions)cache.GetOrAddShared(typeof(Conversions), new Conversions(compilation));
+ }
+ return operators;
}
#region TypePair (for caching)
@@ -147,7 +153,7 @@ public Conversion StandardImplicitConversion(IType fromType, IType toType)
return c;
if (NullLiteralConversion(fromType, toType))
return Conversion.NullLiteralConversion;
- if (ImplicitReferenceConversion(fromType, toType))
+ if (ImplicitReferenceConversion(fromType, toType, 0))
return Conversion.ImplicitReferenceConversion;
if (BoxingConversion(fromType, toType))
return Conversion.BoxingConversion;
@@ -176,7 +182,7 @@ public bool IsConstraintConvertible(IType fromType, IType toType)
if (IdentityConversion(fromType, toType))
return true;
- if (ImplicitReferenceConversion(fromType, toType))
+ if (ImplicitReferenceConversion(fromType, toType, 0))
return true;
if (BoxingConversion(fromType, toType) && !NullableType.IsNullable(fromType))
return true;
@@ -392,7 +398,7 @@ bool NullLiteralConversion(IType fromType, IType toType)
#endregion
#region Implicit Reference Conversion
- bool ImplicitReferenceConversion(IType fromType, IType toType)
+ bool ImplicitReferenceConversion(IType fromType, IType toType, int subtypeCheckNestingDepth)
{
// C# 4.0 spec: §6.1.6
@@ -406,7 +412,7 @@ bool ImplicitReferenceConversion(IType fromType, IType toType)
if (toArray != null) {
// array covariance (the broken kind)
return fromArray.Dimensions == toArray.Dimensions
- && ImplicitReferenceConversion(fromArray.ElementType, toArray.ElementType);
+ && ImplicitReferenceConversion(fromArray.ElementType, toArray.ElementType, subtypeCheckNestingDepth);
}
// conversion from single-dimensional array S[] to IList<T>:
ParameterizedType toPT = toType as ParameterizedType;
@@ -416,47 +422,43 @@ bool ImplicitReferenceConversion(IType fromType, IType toType)
{
// array covariance plays a part here as well (string[] is IList<object>)
return IdentityConversion(fromArray.ElementType, toPT.GetTypeArgument(0))
- || ImplicitReferenceConversion(fromArray.ElementType, toPT.GetTypeArgument(0));
+ || ImplicitReferenceConversion(fromArray.ElementType, toPT.GetTypeArgument(0), subtypeCheckNestingDepth);
}
// conversion from any array to System.Array and the interfaces it implements:
IType systemArray = compilation.FindType(KnownTypeCode.Array);
- return systemArray.Kind != TypeKind.Unknown && (systemArray.Equals(toType) || ImplicitReferenceConversion(systemArray, toType));
+ return systemArray.Kind != TypeKind.Unknown && (systemArray.Equals(toType) || ImplicitReferenceConversion(systemArray, toType, subtypeCheckNestingDepth));
}
// now comes the hard part: traverse the inheritance chain and figure out generics+variance
- return IsSubtypeOf(fromType, toType);
+ return IsSubtypeOf(fromType, toType, subtypeCheckNestingDepth);
}
// Determines whether s is a subtype of t.
// Helper method used for ImplicitReferenceConversion, BoxingConversion and ImplicitTypeParameterConversion
- bool IsSubtypeOf(IType s, IType t)
+ bool IsSubtypeOf(IType s, IType t, int subtypeCheckNestingDepth)
{
// conversion to dynamic + object are always possible
if (t.Kind == TypeKind.Dynamic || t.Equals(objectType))
return true;
- try {
- if (++subtypeCheckNestingDepth > 10) {
- // Subtyping in C# is undecidable
- // (see "On Decidability of Nominal Subtyping with Variance" by Andrew J. Kennedy and Benjamin C. Pierce),
- // so we'll prevent infinite recursions by putting a limit on the nesting depth of variance conversions.
-
- // No real C# code should use generics nested more than 10 levels deep, and even if they do, most of
- // those nestings should not involve variance.
- return false;
- }
- // let GetAllBaseTypes do the work for us
- foreach (IType baseType in s.GetAllBaseTypes()) {
- if (IdentityOrVarianceConversion(baseType, t))
- return true;
- }
+ if (subtypeCheckNestingDepth > 10) {
+ // Subtyping in C# is undecidable
+ // (see "On Decidability of Nominal Subtyping with Variance" by Andrew J. Kennedy and Benjamin C. Pierce),
+ // so we'll prevent infinite recursions by putting a limit on the nesting depth of variance conversions.
+
+ // No real C# code should use generics nested more than 10 levels deep, and even if they do, most of
+ // those nestings should not involve variance.
return false;
- } finally {
- subtypeCheckNestingDepth--;
}
+ // let GetAllBaseTypes do the work for us
+ foreach (IType baseType in s.GetAllBaseTypes()) {
+ if (IdentityOrVarianceConversion(baseType, t, subtypeCheckNestingDepth + 1))
+ return true;
+ }
+ return false;
}
- bool IdentityOrVarianceConversion(IType s, IType t)
+ bool IdentityOrVarianceConversion(IType s, IType t, int subtypeCheckNestingDepth)
{
ITypeDefinition def = s.GetDefinition();
if (def != null && def.Equals(t.GetDefinition())) {
@@ -472,11 +474,11 @@ bool IdentityOrVarianceConversion(IType s, IType t)
ITypeParameter xi = def.TypeParameters[i];
switch (xi.Variance) {
case VarianceModifier.Covariant:
- if (!ImplicitReferenceConversion(si, ti))
+ if (!ImplicitReferenceConversion(si, ti, subtypeCheckNestingDepth))
return false;
break;
case VarianceModifier.Contravariant:
- if (!ImplicitReferenceConversion(ti, si))
+ if (!ImplicitReferenceConversion(ti, si, subtypeCheckNestingDepth))
return false;
break;
default:
@@ -513,7 +515,7 @@ bool BoxingConversion(IType fromType, IType toType)
// C# 4.0 spec: §6.1.7
fromType = NullableType.GetUnderlyingType(fromType);
if (fromType.IsReferenceType == false && toType.IsReferenceType == true)
- return IsSubtypeOf(fromType, toType);
+ return IsSubtypeOf(fromType, toType, 0);
else
return false;
}
@@ -523,7 +525,7 @@ bool UnboxingConversion(IType fromType, IType toType)
// C# 4.0 spec: §6.2.5
toType = NullableType.GetUnderlyingType(toType);
if (fromType.IsReferenceType == true && toType.IsReferenceType == false)
- return IsSubtypeOf(toType, fromType);
+ return IsSubtypeOf(toType, fromType, 0);
else
return false;
}
@@ -570,7 +572,7 @@ bool ImplicitTypeParameterConversion(IType fromType, IType toType)
return false; // not a type parameter
if (fromType.IsReferenceType == true)
return false; // already handled by ImplicitReferenceConversion
- return IsSubtypeOf(fromType, toType);
+ return IsSubtypeOf(fromType, toType, 0);
}
bool ExplicitTypeParameterConversion(IType fromType, IType toType)
View
34 README
@@ -9,12 +9,9 @@ Features:
- Lots of C# refactorings
- VB support is not implemented yet.
-
-Dependencies:
- .NET 4.0
- Mono.Cecil 0.9.5
- NRefactory contains a modified copy of mcs (Mono's C# compiler) for the C# parser.
-
+How to download:
+ - Binaries are provided as a NuGet package (http://nuget.org/packages/ICSharpCode.NRefactory)
+ - Sourcecode is available on GitHub (https://github.com/icsharpcode/NRefactory)
How to compile:
1. Get Mono.Cecil
@@ -23,6 +20,12 @@ How to compile:
2. Open NRefactory.sln in your favorite .NET IDE and compile.
+Dependencies:
+ .NET 4.0
+ Mono.Cecil 0.9.5
+ NRefactory contains a modified copy of mcs (Mono's C# compiler) for the C# parser.
+
+
Namespace overview:
ICSharpCode.NRefactory assembly
@@ -38,7 +41,8 @@ ICSharpCode.NRefactory assembly
Contains default implementations for the type system interfaces.
ICSharpCode.NRefactory.Semantics:
- Contains classes used to represent the semantics of a language element.
+ Contains classes (ResolveResults) used to represent the semantics of a language element.
+ ResolveResults can have child results, thus forming semantic trees.
ICSharpCode.NRefactory.Documentation:
Classes for working with .xml documentation files.
@@ -64,15 +68,25 @@ ICSharpCode.NRefactory.CSharp assembly
ICSharpCode.NRefactory.CSharp.Analysis:
Semantic analysis for C# (additional analysis functionality)
-
+
ICSharpCode.NRefactory.CSharp.TypeSystem:
Contains type system implementations specific to C#.
-
+
+ ICSharpCode.NRefactory.CSharp.Refactoring:
+ Infrastructure for refactorings; and several built-in refactorings.
ICSharpCode.NRefactory.VB assembly
ICSharpCode.NRefactory.VB:
Abstract Syntax Tree for VB
+ICSharpCode.NRefactory.Xml assembly
+ ICSharpCode.NRefactory.Xml:
+ Error-tolerant XML parser. Supports incremental parsing.
+ When tags don't match correctly, the parser uses a heuristic to guess the parse tree.
+ The heuristic tries to minimize the edit distance from the erroneous document to
+ the fixed document, and it also considers the indentation.
+
+
Null-Object pattern:
The NRefactory library makes extensive use of the null object pattern.
@@ -181,7 +195,7 @@ A: This question is a bit difficult to answer.
so it's thread-safe but slow.
Some instance methods may use hidden instance state, so it is not safe to e.g use an instance
- of the CSharp.Resolver.Conversions class concurrently.
+ of the CSharp.Resolver.TypeInference class concurrently.
Instead, you need to create an instance on every thread.
The type system itself is thread-safe. Both the unresolved and the resolved type systems are

0 comments on commit 3df0cd3

Please sign in to comment.