diff --git a/Directory.Build.props b/Directory.Build.props
index e0cd93ede..17bbe6dff 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -13,9 +13,9 @@
all
runtime; build; native; contentfiles; analyzers
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
+ false
+ Analyzer
+
diff --git a/pythonnet.sln b/pythonnet.sln
index 4ca4fb285..3151454c3 100644
--- a/pythonnet.sln
+++ b/pythonnet.sln
@@ -1,4 +1,4 @@
-Microsoft Visual Studio Solution File, Format Version 12.00
+Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30717.126
MinimumVisualStudioVersion = 15.0.26124.0
@@ -46,6 +46,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{142A6752
Directory.Build.props = Directory.Build.props
EndProjectSection
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{D4963EF0-46CD-43AF-939D-BA47C1B091D1}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NonCopyable", "src\noncopyable_analyzer\NonCopyable.csproj", "{CA041F36-A4C2-4B18-9501-F670FDED87F4}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -152,6 +156,18 @@ Global
{35CBBDEB-FC07-4D04-9D3E-F88FC180110B}.Release|x64.Build.0 = Release|Any CPU
{35CBBDEB-FC07-4D04-9D3E-F88FC180110B}.Release|x86.ActiveCfg = Release|Any CPU
{35CBBDEB-FC07-4D04-9D3E-F88FC180110B}.Release|x86.Build.0 = Release|Any CPU
+ {CA041F36-A4C2-4B18-9501-F670FDED87F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CA041F36-A4C2-4B18-9501-F670FDED87F4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CA041F36-A4C2-4B18-9501-F670FDED87F4}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {CA041F36-A4C2-4B18-9501-F670FDED87F4}.Debug|x64.Build.0 = Debug|Any CPU
+ {CA041F36-A4C2-4B18-9501-F670FDED87F4}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {CA041F36-A4C2-4B18-9501-F670FDED87F4}.Debug|x86.Build.0 = Debug|Any CPU
+ {CA041F36-A4C2-4B18-9501-F670FDED87F4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CA041F36-A4C2-4B18-9501-F670FDED87F4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CA041F36-A4C2-4B18-9501-F670FDED87F4}.Release|x64.ActiveCfg = Release|Any CPU
+ {CA041F36-A4C2-4B18-9501-F670FDED87F4}.Release|x64.Build.0 = Release|Any CPU
+ {CA041F36-A4C2-4B18-9501-F670FDED87F4}.Release|x86.ActiveCfg = Release|Any CPU
+ {CA041F36-A4C2-4B18-9501-F670FDED87F4}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -159,4 +175,7 @@ Global
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C8845072-C642-4858-8627-27E862AD21BB}
EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {CA041F36-A4C2-4B18-9501-F670FDED87F4} = {D4963EF0-46CD-43AF-939D-BA47C1B091D1}
+ EndGlobalSection
EndGlobal
diff --git a/src/noncopyable_analyzer/LICENSE b/src/noncopyable_analyzer/LICENSE
new file mode 100644
index 000000000..76b0b9f58
--- /dev/null
+++ b/src/noncopyable_analyzer/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017 Nobuyuki Iwanaga
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/src/noncopyable_analyzer/NonCopyable.csproj b/src/noncopyable_analyzer/NonCopyable.csproj
new file mode 100644
index 000000000..ef8890d2b
--- /dev/null
+++ b/src/noncopyable_analyzer/NonCopyable.csproj
@@ -0,0 +1,27 @@
+
+
+
+ netstandard2.0
+ false
+ True
+ latest
+
+
+
+ PythonNet.NonCopyableAnalyzer
+ Nobuyuki Iwanaga
+ https://github.com/ufcpp/NonCopyableAnalyzer/blob/master/LICENSE
+ https://github.com/ufcpp/NonCopyableAnalyzer
+ false
+ Analyzer for Non-copyable struct
+ Fixed false positive on conversion operators with in argument.
+ NonCopyable, analyzers
+ true
+
+
+
+
+
+
+
+
diff --git a/src/noncopyable_analyzer/NonCopyableAnalyzer.cs b/src/noncopyable_analyzer/NonCopyableAnalyzer.cs
new file mode 100644
index 000000000..ffbbbacb4
--- /dev/null
+++ b/src/noncopyable_analyzer/NonCopyableAnalyzer.cs
@@ -0,0 +1,256 @@
+using System.Collections.Immutable;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Operations;
+
+namespace NonCopyable
+{
+ [DiagnosticAnalyzer(LanguageNames.CSharp)]
+ public class NonCopyableAnalyzer : DiagnosticAnalyzer
+ {
+ private static DiagnosticDescriptor CreateRule(int num, string type)
+ => new DiagnosticDescriptor("NoCopy" + num.ToString("00"), "non-copyable", "🚫 " + type + ". '{0}' is non-copyable.", "Correction", DiagnosticSeverity.Error, isEnabledByDefault: true);
+
+ private static DiagnosticDescriptor FieldDeclarationRule = CreateRule(1, "field declaration");
+ private static DiagnosticDescriptor InitializerRule = CreateRule(2, "initializer");
+ private static DiagnosticDescriptor AssignmentRule = CreateRule(3, "assignment");
+ private static DiagnosticDescriptor ArgumentRule = CreateRule(4, "argument");
+ private static DiagnosticDescriptor ReturnRule = CreateRule(5, "return");
+ private static DiagnosticDescriptor ConversionRule = CreateRule(6, "conversion");
+ private static DiagnosticDescriptor PatternRule = CreateRule(7, "pattern matching");
+ private static DiagnosticDescriptor TupleRule = CreateRule(8, "tuple");
+ private static DiagnosticDescriptor MemberRule = CreateRule(9, "member reference");
+ private static DiagnosticDescriptor ReadOnlyInvokeRule = CreateRule(10, "readonly invoke");
+ private static DiagnosticDescriptor GenericConstraintRule = CreateRule(11, "generic constraint");
+ private static DiagnosticDescriptor DelegateRule = CreateRule(12, "delegate");
+
+ public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(FieldDeclarationRule, InitializerRule, AssignmentRule, ArgumentRule, ReturnRule, ConversionRule, PatternRule, TupleRule, MemberRule, ReadOnlyInvokeRule, GenericConstraintRule, DelegateRule);
+
+ public override void Initialize(AnalysisContext context)
+ {
+ context.RegisterCompilationStartAction(csc =>
+ {
+ csc.RegisterOperationAction(oc =>
+ {
+ var op = (ISymbolInitializerOperation)oc.Operation;
+ CheckCopyability(oc, op.Value, InitializerRule);
+ }, OperationKind.FieldInitializer,
+ OperationKind.ParameterInitializer,
+ OperationKind.PropertyInitializer,
+ OperationKind.VariableInitializer);
+
+ csc.RegisterOperationAction(oc =>
+ {
+ // including member initializer
+ // including collection element initializer
+ var op = (ISimpleAssignmentOperation)oc.Operation;
+ if (op.IsRef) return;
+ CheckCopyability(oc, op.Value, AssignmentRule);
+ }, OperationKind.SimpleAssignment);
+
+ csc.RegisterOperationAction(oc =>
+ {
+ // including non-ref extension method invocation
+ var op = (IArgumentOperation)oc.Operation;
+ if (op.Parameter.RefKind != RefKind.None) return;
+ CheckCopyability(oc, op.Value, ArgumentRule);
+ }, OperationKind.Argument);
+
+ csc.RegisterOperationAction(oc =>
+ {
+ var op = (IReturnOperation)oc.Operation;
+ if (op.ReturnedValue == null) return;
+ CheckCopyability(oc, op.ReturnedValue, ReturnRule);
+ }, OperationKind.Return,
+ OperationKind.YieldReturn);
+
+ csc.RegisterOperationAction(oc =>
+ {
+ var op = (IConversionOperation)oc.Operation;
+ var v = op.Operand;
+ if (v.Kind == OperationKind.DefaultValue) return;
+ var t = v.Type;
+ if (!t.IsNonCopyable()) return;
+
+ if (op.OperatorMethod != null && op.OperatorMethod.Parameters.Length == 1)
+ {
+ var parameter = op.OperatorMethod.Parameters[0];
+ if (parameter.RefKind != RefKind.None) return;
+ }
+
+ if (op.Parent is IForEachLoopOperation &&
+ op == ((IForEachLoopOperation)op.Parent).Collection &&
+ op.Conversion.IsIdentity)
+ {
+ return;
+ }
+
+ oc.ReportDiagnostic(Error(v.Syntax, ConversionRule, t.Name));
+ }, OperationKind.Conversion);
+
+ csc.RegisterOperationAction(oc =>
+ {
+ var op = (IArrayInitializerOperation)oc.Operation;
+
+ if (!((IArrayTypeSymbol)((IArrayCreationOperation)op.Parent).Type).ElementType.IsNonCopyable()) return;
+
+ foreach (var v in op.ElementValues)
+ {
+ CheckCopyability(oc, v, InitializerRule);
+ }
+ }, OperationKind.ArrayInitializer);
+
+ csc.RegisterOperationAction(oc =>
+ {
+ var op = (IDeclarationPatternOperation)oc.Operation;
+ var t = ((ILocalSymbol)op.DeclaredSymbol).Type;
+ if (!t.IsNonCopyable()) return;
+ oc.ReportDiagnostic(Error(op.Syntax, PatternRule, t.Name));
+ }, OperationKind.DeclarationPattern);
+
+ csc.RegisterOperationAction(oc =>
+ {
+ var op = (ITupleOperation)oc.Operation;
+
+ // exclude ParenthesizedVariableDesignationSyntax
+ if (op.Syntax.Kind() != SyntaxKind.TupleExpression) return;
+
+ foreach (var v in op.Elements)
+ {
+ CheckCopyability(oc, v, TupleRule);
+ }
+ }, OperationKind.Tuple);
+
+ csc.RegisterOperationAction(oc =>
+ {
+ // instance property/event should not be referenced with in parameter/ref readonly local/readonly field
+ var op = (IMemberReferenceOperation)oc.Operation;
+ CheckInstanceReadonly(oc, op.Instance, MemberRule);
+ }, OperationKind.PropertyReference,
+ OperationKind.EventReference);
+
+ csc.RegisterOperationAction(oc =>
+ {
+ // instance method should not be invoked with in parameter/ref readonly local/readonly field
+ var op = (IInvocationOperation)oc.Operation;
+
+ CheckGenericConstraints(oc, op, GenericConstraintRule);
+ CheckInstanceReadonly(oc, op.Instance, ReadOnlyInvokeRule);
+
+ }, OperationKind.Invocation);
+
+ csc.RegisterOperationAction(oc => {
+ var op = (IDynamicInvocationOperation)oc.Operation;
+
+ foreach(var arg in op.Arguments) {
+ if (!arg.Type.IsNonCopyable()) continue;
+
+ oc.ReportDiagnostic(Error(arg.Syntax, GenericConstraintRule));
+ }
+
+ }, OperationKind.DynamicInvocation);
+
+ csc.RegisterOperationAction(oc =>
+ {
+ // delagate creation
+ var op = (IMemberReferenceOperation)oc.Operation;
+ if (op.Instance == null) return;
+ if (!op.Instance.Type.IsNonCopyable()) return;
+ oc.ReportDiagnostic(Error(op.Instance.Syntax, DelegateRule, op.Instance.Type.Name));
+ }, OperationKind.MethodReference);
+
+ csc.RegisterSymbolAction(sac =>
+ {
+ var f = (IFieldSymbol)sac.Symbol;
+ if (f.IsStatic) return;
+ if (!f.Type.IsNonCopyable()) return;
+ if (f.ContainingType.IsReferenceType) return;
+ if (f.ContainingType.IsNonCopyable()) return;
+ sac.ReportDiagnostic(Error(f.DeclaringSyntaxReferences[0].GetSyntax(), FieldDeclarationRule, f.Type.Name));
+ }, SymbolKind.Field);
+ });
+
+ // not supported yet:
+ // OperationKind.CompoundAssignment,
+ // OperationKind.UnaryOperator,
+ // OperationKind.BinaryOperator,
+ }
+
+ private static void CheckGenericConstraints(in OperationAnalysisContext oc, IInvocationOperation op, DiagnosticDescriptor rule)
+ {
+ var m = op.TargetMethod;
+
+ if (m.IsGenericMethod)
+ {
+ var parameters = m.TypeParameters;
+ var arguments = m.TypeArguments;
+ for (int i = 0; i < parameters.Length; i++)
+ {
+ var p = parameters[i];
+ var a = arguments[i];
+
+ if (a.IsNonCopyable() && !p.IsNonCopyable())
+ oc.ReportDiagnostic(Error(op.Syntax, rule, a.Name));
+ }
+ }
+ }
+
+ private static void CheckInstanceReadonly(in OperationAnalysisContext oc, IOperation instance, DiagnosticDescriptor rule)
+ {
+ if (instance == null) return;
+
+ var t = instance.Type;
+ if (!t.IsNonCopyable()) return;
+
+ if (IsInstanceReadonly(instance))
+ {
+ oc.ReportDiagnostic(Error(instance.Syntax, rule, t.Name));
+ }
+ }
+
+ private static Diagnostic Error(SyntaxNode at, DiagnosticDescriptor rule, string name = null)
+ => name is null
+ ? Diagnostic.Create(rule, at.GetLocation())
+ : Diagnostic.Create(rule, at.GetLocation(), name);
+
+ private static bool IsInstanceReadonly(IOperation instance)
+ {
+ bool isReadOnly = false;
+ switch (instance)
+ {
+ case IFieldReferenceOperation r:
+ isReadOnly = r.Field.IsReadOnly;
+ break;
+ case ILocalReferenceOperation r:
+ isReadOnly = r.Local.RefKind == RefKind.In;
+ break;
+ case IParameterReferenceOperation r:
+ isReadOnly = r.Parameter.RefKind == RefKind.In;
+ break;
+ }
+
+ return isReadOnly;
+ }
+
+ private static bool HasNonCopyableParameter(IMethodSymbol m)
+ {
+ foreach (var p in m.Parameters)
+ {
+ if(p.RefKind == RefKind.None)
+ {
+ if (p.Type.IsNonCopyable()) return true;
+ }
+ }
+ return false;
+ }
+
+ private static void CheckCopyability(in OperationAnalysisContext oc, IOperation v, DiagnosticDescriptor rule)
+ {
+ var t = v.Type;
+ if (!t.IsNonCopyable()) return;
+ if (v.CanCopy()) return;
+ oc.ReportDiagnostic(Error(v.Syntax, rule, t.Name));
+ }
+ }
+}
diff --git a/src/noncopyable_analyzer/TypeExtensions.cs b/src/noncopyable_analyzer/TypeExtensions.cs
new file mode 100644
index 000000000..a1570ad68
--- /dev/null
+++ b/src/noncopyable_analyzer/TypeExtensions.cs
@@ -0,0 +1,101 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Operations;
+
+namespace NonCopyable
+{
+ public static class TypeExtensions
+ {
+ public static bool IsNonCopyable(this ITypeSymbol t)
+ {
+ if (t == null) return false;
+ if (t.TypeKind != TypeKind.Struct && t.TypeKind != TypeKind.TypeParameter) return false;
+
+ if (HasNonCopyableAttribute(t)) return true;
+
+ if (t.TypeKind == TypeKind.Struct)
+ {
+ foreach (var ifType in t.AllInterfaces)
+ {
+ if (HasNonCopyableAttribute(ifType)) return true;
+ }
+ }
+ else
+ {
+ foreach (var constraint in ((ITypeParameterSymbol)t).ConstraintTypes)
+ {
+ if (HasNonCopyableAttribute(constraint)) return true;
+ }
+
+ }
+
+ return false;
+ }
+
+ private static bool HasNonCopyableAttribute(ITypeSymbol t)
+ {
+ foreach (var a in t.GetAttributes())
+ {
+ var str = a.AttributeClass.Name;
+ if (str.EndsWith("NonCopyable") || str.EndsWith("NonCopyableAttribute")) return true;
+ }
+
+ return false;
+ }
+
+ private static SyntaxList GetAttributes(this SyntaxNode syntax)
+ {
+ switch (syntax)
+ {
+ case StructDeclarationSyntax s:
+ return s.AttributeLists;
+ case TypeParameterSyntax s:
+ return s.AttributeLists;
+ default:
+ return default;
+ }
+ }
+
+ ///
+ /// test whether op is copyable or not even when it is a non-copyable instance.
+ ///
+ public static bool CanCopy(this IOperation op)
+ {
+ var k = op.Kind;
+
+ if (k == OperationKind.Conversion)
+ {
+ var operandKind = ((IConversionOperation)op).Operand.Kind;
+ // default literal (invalid if LangVersion < 7.1)
+ if (operandKind == OperationKind.DefaultValue || operandKind == OperationKind.Invalid) return true;
+ }
+
+ if (k == OperationKind.LocalReference || k == OperationKind.FieldReference || k == OperationKind.PropertyReference || k == OperationKind.ArrayElementReference)
+ {
+ //need help: how to get ref-ness from IOperation?
+ var parent = op.Syntax.Parent.Kind();
+ if (parent == SyntaxKind.RefExpression) return true;
+ }
+
+ if (k == OperationKind.Conditional)
+ {
+ var cond = (IConditionalOperation)op;
+ return cond.WhenFalse.CanCopy() && cond.WhenFalse.CanCopy();
+ }
+
+ return k == OperationKind.ObjectCreation
+ || k == OperationKind.DefaultValue
+ || k == OperationKind.Literal
+ || k == OperationKind.Invocation
+ // workaround for https://github.com/dotnet/roslyn/issues/49751
+ || !IsValid(k) && op.Syntax is InvocationExpressionSyntax;
+
+ //todo: should return value be OK?
+ //todo: move semantics
+ }
+
+ static bool IsValid(OperationKind kind)
+ => kind != OperationKind.None && kind != OperationKind.Invalid;
+ }
+}