Skip to content

Commit 6fe8c66

Browse files
authored
Allow static class conversion for classes with implicit/deleted constructors (#1923)
* Allow static class conversion for classes with implicitly defined constructor * Fix static being applied to classes without static methods/fields * Fix static class edge cases
1 parent f58974a commit 6fe8c66

File tree

5 files changed

+148
-34
lines changed

5 files changed

+148
-34
lines changed

src/AST/Class.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,9 +149,7 @@ public Class BaseClass
149149

150150
public bool HasNonIgnoredBase =>
151151
HasBaseClass && !IsValueType
152-
&& BaseClass != null
153-
&& !BaseClass.IsValueType
154-
&& BaseClass.IsGenerated;
152+
&& BaseClass is { IsValueType: false, IsGenerated: true };
155153

156154
public bool NeedsBase => HasNonIgnoredBase && IsGenerated;
157155

src/Generator.Tests/Passes/TestPasses.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,32 @@ public void TestCheckEnumsPass()
6666
Assert.IsTrue(ucharClassEnum.BuiltinType.Type == PrimitiveType.UChar);
6767
}
6868

69+
[Test]
70+
public void TestCheckStaticClassPass()
71+
{
72+
var staticClass = AstContext.Class("TestCheckStaticClass");
73+
var staticStruct = AstContext.Class("TestCheckStaticStruct");
74+
var staticClassDeletedCtor = AstContext.Class("TestCheckStaticClassDeleted");
75+
var nonStaticClass = AstContext.Class("TestCheckNonStaticClass");
76+
var nonStaticEmptyClass = AstContext.Class("TestCommentsPass");
77+
78+
Assert.IsFalse(staticClass.IsStatic);
79+
Assert.IsFalse(staticStruct.IsStatic);
80+
Assert.IsFalse(staticClassDeletedCtor.IsStatic);
81+
Assert.IsFalse(nonStaticClass.IsStatic);
82+
Assert.IsFalse(nonStaticEmptyClass.IsStatic);
83+
84+
passBuilder.AddPass(new CheckStaticClassPass());
85+
passBuilder.RunPasses(pass => pass.VisitASTContext(AstContext));
86+
87+
Assert.IsTrue(staticClass.IsStatic, "`TestCheckStaticClass` should be static");
88+
Assert.IsTrue(staticStruct.IsStatic, "`TestCheckStaticStruct` should be static");
89+
Assert.IsTrue(staticClassDeletedCtor.IsStatic, "`TestCheckStaticClassDeleted` should be static");
90+
91+
Assert.IsFalse(nonStaticClass.IsStatic, "`TestCheckNonStaticClass` should NOT be static, since it has a private data field with default ctor");
92+
Assert.IsFalse(nonStaticEmptyClass.IsStatic, "`TestCommentsPass` should NOT be static, since it doesn't have any static declarations");
93+
}
94+
6995
[Test]
7096
public void TestFunctionToInstancePass()
7197
{

src/Generator/Driver.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ public void SetupPasses(ILibrary library)
230230

231231
passes.AddPass(new FindSymbolsPass());
232232
passes.AddPass(new CheckMacroPass());
233-
passes.AddPass(new CheckStaticClass());
233+
passes.AddPass(new CheckStaticClassPass());
234234

235235
if (Options.IsCLIGenerator || Options.IsCSharpGenerator || Options.IsCppGenerator)
236236
{

src/Generator/Passes/CheckStaticClass.cs renamed to src/Generator/Passes/CheckStaticClassPass.cs

Lines changed: 66 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ namespace CppSharp.Passes
77
/// <summary>
88
/// Checks for classes that should be bound as static classes.
99
/// </summary>
10-
public class CheckStaticClass : TranslationUnitPass
10+
public class CheckStaticClassPass : TranslationUnitPass
1111
{
12-
public CheckStaticClass()
12+
public CheckStaticClassPass()
1313
=> VisitOptions.ResetFlags(VisitFlags.ClassMethods);
1414

1515
public override bool VisitDeclaration(Declaration decl)
@@ -32,8 +32,7 @@ static bool IsProtectedClassMember(Declaration decl)
3232
if (decl.Access != AccessSpecifier.Protected)
3333
return false;
3434

35-
var @class = decl.Namespace as Class;
36-
return @class != null && @class.IsStatic;
35+
return decl.Namespace is Class { IsStatic: true };
3736
}
3837

3938
static void SetDeclarationAccessToPrivate(Declaration decl)
@@ -42,7 +41,7 @@ static void SetDeclarationAccessToPrivate(Declaration decl)
4241
// as an internal in the final C# wrapper.
4342
decl.Access = AccessSpecifier.Private;
4443

45-
// We need to explicity set the generation else the
44+
// We need to explicitly set the generation else the
4645
// now private declaration will get filtered out later.
4746
decl.GenerationKind = GenerationKind.Generate;
4847
}
@@ -51,53 +50,90 @@ static bool ReturnsClassInstance(Function function)
5150
{
5251
var returnType = function.ReturnType.Type.Desugar();
5352

54-
var tag = returnType as TagType;
55-
if (tag == null)
53+
if (returnType is not TagType tag)
5654
returnType.IsPointerTo(out tag);
5755

58-
if (tag == null)
56+
var decl = tag?.Declaration;
57+
if (decl is not Class)
5958
return false;
6059

6160
var @class = (Class)function.Namespace;
62-
var decl = tag.Declaration;
61+
return @class.QualifiedOriginalName == decl.QualifiedOriginalName;
62+
}
6363

64-
if (!(decl is Class))
65-
return false;
64+
static bool AcceptsClassInstance(Function function)
65+
{
66+
return function.Parameters.Any(param =>
67+
{
68+
var paramType = param.Type.Desugar();
6669

67-
return @class.QualifiedOriginalName == decl.QualifiedOriginalName;
70+
if (paramType is not TagType tag)
71+
paramType.IsPointerTo(out tag);
72+
73+
var decl = tag?.Declaration;
74+
if (decl is not Class)
75+
return false;
76+
77+
var @class = (Class)function.Namespace;
78+
return @class.QualifiedOriginalName == decl.QualifiedOriginalName;
79+
}
80+
);
6881
}
6982

7083
public override bool VisitClassDecl(Class @class)
7184
{
72-
// If the class has any non-private constructors then it cannot
73-
// be bound as a static class and we bail out early.
74-
if (@class.Constructors.Any(m =>
75-
!(m.IsCopyConstructor || m.IsMoveConstructor)
76-
&& m.Access != AccessSpecifier.Private))
85+
// If the class is to be used as an opaque type, then it cannot be
86+
// bound as static.
87+
if (@class.IsOpaque)
88+
return false;
89+
90+
if (@class.IsDependent)
91+
return false;
92+
93+
// Polymorphic classes are currently not supported.
94+
// TODO: We could support this if the base class is also static, since it's composition then.
95+
if (@class.IsPolymorphic)
96+
return false;
97+
98+
// Make sure we have at least one accessible static method or field
99+
if (!@class.Methods.Any(m => m.Kind == CXXMethodKind.Normal && m.Access != AccessSpecifier.Private && m.IsStatic)
100+
&& @class.Variables.All(v => v.Access == AccessSpecifier.Private))
77101
return false;
78102

79103
// Check for any non-static fields or methods, in which case we
80104
// assume the class is not meant to be static.
81105
// Note: Static fields are represented as variables in the AST.
82-
if (@class.Fields.Any() ||
83-
@class.Methods.Any(m => m.Kind == CXXMethodKind.Normal
84-
&& !m.IsStatic))
106+
if (@class.Fields.Count != 0)
85107
return false;
86108

87-
// Check for any static function that return a pointer to the class.
88-
// If one exists, we assume it's a factory function and the class is
89-
// not meant to be static. It's a simple heuristic but it should be
90-
// good enough for the time being.
91-
if (@class.Functions.Any(m => !m.IsOperator && ReturnsClassInstance(m)) ||
92-
@class.Methods.Any(m => !m.IsOperator && ReturnsClassInstance(m)))
109+
if (@class.Methods.Any(m => m.Kind == CXXMethodKind.Normal && !m.IsStatic))
93110
return false;
94111

95-
// If the class is to be used as an opaque type, then it cannot be
96-
// bound as static.
97-
if (@class.IsOpaque)
112+
if (@class.Constructors.Any(m =>
113+
{
114+
// Implicit constructors are not user-defined, so assume this was unintentional.
115+
if (m.IsImplicit)
116+
return false;
117+
118+
// Ignore deleted constructors.
119+
if (m.IsDeleted)
120+
return false;
121+
122+
// If the class has a copy or move constructor, it cannot be static.
123+
if (m.IsCopyConstructor || m.IsMoveConstructor)
124+
return true;
125+
126+
// If the class has any (user defined) non-private constructors then it cannot be static
127+
return m.Access != AccessSpecifier.Private;
128+
}))
129+
{
98130
return false;
131+
}
99132

100-
if (@class.IsDependent)
133+
// Check for any static function that accepts/returns a pointer to the class.
134+
// If one exists, we assume it's not meant to be static
135+
if (@class.Functions.Any(m => !m.IsOperator && (ReturnsClassInstance(m) || AcceptsClassInstance(m))) ||
136+
@class.Methods.Any(m => !m.IsOperator && !m.IsConstructor && (ReturnsClassInstance(m) || AcceptsClassInstance(m))))
101137
return false;
102138

103139
// TODO: We should take C++ friends into account here, they might allow

tests/dotnet/Native/Passes.h

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,60 @@ struct TestCheckAmbiguousFunctionsPass
130130
int Method(int x) const;
131131
};
132132

133+
class TestCheckStaticClass
134+
{
135+
public:
136+
static int Method();
137+
static int Method(int x);
138+
139+
constexpr static float ConstExprStatic = 3.0f;
140+
inline static float InlineStatic = 1.0f;
141+
142+
private:
143+
inline static float PrivateInlineStatic = 1.0f;
144+
};
145+
146+
struct TestCheckStaticStruct
147+
{
148+
static int Method();
149+
static int Method(int x);
150+
151+
constexpr static float ConstExprStatic = 3.0f;
152+
inline static float InlineStatic = 1.0f;
153+
};
154+
155+
class TestCheckStaticClassDeleted
156+
{
157+
public:
158+
TestCheckStaticClassDeleted() = delete;
159+
160+
static int Method();
161+
static int Method(int x);
162+
163+
constexpr static float ConstExprStatic = 3.0f;
164+
inline static float InlineStatic = 1.0f;
165+
166+
private:
167+
inline static float PrivateInlineStatic = 1.0f;
168+
};
169+
170+
class TestCheckNonStaticClass
171+
{
172+
public:
173+
TestCheckNonStaticClass() = default;
174+
175+
static int Method();
176+
static int Method(int x);
177+
178+
constexpr static float ConstExprStatic = 3.0f;
179+
inline static float InlineStatic = 1.0f;
180+
181+
private:
182+
inline static float PrivateInlineStatic = 1.0f;
183+
184+
float NonStatic = 1.0f;
185+
};
186+
133187
#define CS_INTERNAL
134188
struct TestMethodAsInternal
135189
{

0 commit comments

Comments
 (0)