blob: 28b24c123b9d130b19c880cbc5855235ba01e18a [file]
// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import '../code_generator.dart';
import '../context.dart';
import '../visitor/ast.dart';
import 'binding_string.dart';
import 'local_variables.dart';
import 'scope.dart';
import 'utils.dart';
import 'writer.dart';
enum CppMethodKind { constructor, destructor, method }
/// A method or constructor belonging to a C++ class.
class CppMethod extends AstNode with HasLocalScope {
final Symbol name;
final String originalName;
final Type returnType;
final List<Parameter> parameters;
final bool isConstant;
final bool isStatic;
final CppMethodKind kind;
CppMethod({
required this.name,
required this.originalName,
required this.returnType,
required this.parameters,
required this.isConstant,
this.isStatic = false,
this.kind = CppMethodKind.method,
});
bool get isConstructor => kind == .constructor;
bool get isDestructor => kind == .destructor;
@override
void visit(Visitation visitation) => visitation.visitCppMethod(this);
@override
void visitChildren(Visitor visitor) {
super.visitChildren(visitor);
visitor.visit(name);
visitor.visit(returnType);
visitor.visitAll(parameters);
}
}
class CppMember extends CompoundMember {
/// Whether this field is declared `const` in the C++ source.
///
/// A const field can only have a getter generated; mutable fields get both
/// a getter and a setter.
final bool isConst;
CppMember({
super.originalName,
required super.name,
required super.type,
super.dartDoc,
required this.isConst,
});
}
/// A binding for a C++ class.
class CppClass extends BindingType with HasLocalScope {
final Context context;
final List<CppMethod> methods;
final List<CppMember> fields;
CppClass({
super.usr,
super.originalName,
required super.name,
super.dartDoc,
required this.context,
required this.methods,
required this.fields,
});
@override
void visit(Visitation visitation) => visitation.visitCppClass(this);
@override
String convertDartTypeToFfiDartType(
Context context,
String value, {
required bool objCRetain,
required bool objCAutorelease,
required LocalVariables localVariables,
}) => '$value._ptr';
@override
BindingString toBindingString(Writer w) {
final s = StringBuffer();
final ctx = w.context;
final ffiPrefix = ctx.libs.prefix(ffiImport);
final ptrVoid = '$ffiPrefix.Pointer<$ffiPrefix.Void>';
// Helper to build a comma-separated Dart parameter list.
String dartParamList(Iterable<Parameter> params) =>
params.map((p) => '${p.type.getDartType(ctx)} ${p.name}').join(', ');
final classMethods = methods.where((m) => m.kind == .method).toList();
final constructors = methods.where((m) => m.kind == .constructor).toList();
final destructor = methods.where((m) => m.kind == .destructor).singleOrNull;
final deleteSymbol = '${name}_delete';
final deleteGlue = '_$deleteSymbol';
s.write(makeDartDoc(dartDoc));
s.write('''
class $name {
// ignore: unused_field
final $ptrVoid _ptr;
$name._(this._ptr);
''');
for (final ctor in constructors) {
final glueName = ctor.name.name;
final privateName = '_$glueName';
final dartParams = dartParamList(ctor.parameters);
final localVars = LocalVariables(ctor.localScope);
final callArgs = ctor.parameters
.map(
(p) => p.type.sameDartAndFfiDartType
? p.name
: p.type.convertDartTypeToFfiDartType(
ctx,
p.name,
objCRetain: false,
objCAutorelease: false,
localVariables: localVars,
),
)
.join(', ');
s.write('''
factory $name($dartParams) {
${localVars.generateDeclarations()}
return $name._($privateName($callArgs));
}
''');
}
for (final method in classMethods) {
final glue = '_${method.name.name}';
final dartReturn = method.returnType.getDartType(ctx);
final dartParams = dartParamList(method.parameters);
final callArgs = [
if (!method.isStatic) '_ptr',
...method.parameters.map((p) => p.name),
].join(', ');
final staticKeyword = method.isStatic ? 'static ' : '';
s.write(
' $staticKeyword$dartReturn ${method.originalName}($dartParams)'
' => $glue($callArgs);\n',
);
}
if (destructor != null) {
s.write(' void dispose() => $deleteGlue(_ptr);\n');
} else {
s.write(' void dispose() {}\n');
}
s.write('}\n');
// Writes a @Native annotation + external declaration for a glue function.
void writeNativeDecl({
required String symbol,
required String glue,
required String cType,
required String ffiReturn,
required String ffiParams,
}) {
s.write(
makeNativeAnnotation(
w,
nativeType: cType,
dartName: glue,
nativeSymbolName: Namer.cSafeName(symbol),
),
);
s.write('\nexternal $ffiReturn $glue($ffiParams);\n\n');
}
for (final method in methods) {
final symbol = method.name.name;
final glue = '_$symbol';
final cReturn = method.isConstructor
? ptrVoid
: method.returnType.getCType(ctx);
final ffiReturn = method.isConstructor
? ptrVoid
: method.returnType.getFfiDartType(ctx);
final needsSelf = !method.isConstructor && !method.isStatic;
final cParams = [
if (needsSelf) ptrVoid,
...method.parameters.map((p) => p.type.getCType(ctx)),
].join(', ');
final ffiParams = [
if (needsSelf) '$ptrVoid self',
...method.parameters.map(
(p) => '${p.type.getFfiDartType(ctx)} ${p.name}',
),
].join(', ');
writeNativeDecl(
symbol: symbol,
glue: glue,
cType: '$cReturn Function($cParams)',
ffiReturn: ffiReturn,
ffiParams: ffiParams,
);
}
return BindingString(
type: BindingStringType.cppClass,
string: s.toString(),
);
}
@override
String getCType(Context context) => name;
@override
bool get sameFfiDartAndCType => true;
@override
bool get sameDartAndCType => false;
@override
bool get sameDartAndFfiDartType => false;
@override
void visitChildren(Visitor visitor) {
super.visitChildren(visitor);
visitor.visitAll(methods);
visitor.visitAll(fields);
visitor.visit(ffiImport);
}
}