From e29935f5f5fe207bea65f9a4282aebb5bce332e9 Mon Sep 17 00:00:00 2001 From: tarrinneal Date: Fri, 26 Jun 2026 15:49:51 -0700 Subject: [PATCH 1/3] add support for consts --- packages/pigeon/CHANGELOG.md | 4 + packages/pigeon/README.md | 15 ++ .../pigeon/example/app/pigeons/messages.dart | 7 + packages/pigeon/lib/src/ast.dart | 40 +++- .../pigeon/lib/src/cpp/cpp_generator.dart | 24 +++ .../pigeon/lib/src/dart/dart_generator.dart | 27 +++ packages/pigeon/lib/src/generator.dart | 12 ++ packages/pigeon/lib/src/generator_tools.dart | 22 ++- .../lib/src/gobject/gobject_generator.dart | 30 +++ .../pigeon/lib/src/java/java_generator.dart | 37 +++- .../lib/src/kotlin/kotlin_generator.dart | 30 +++ .../pigeon/lib/src/objc/objc_generator.dart | 33 +++- .../pigeon/lib/src/pigeon_lib_internal.dart | 185 ++++++++++++++++++ .../pigeon/lib/src/swift/swift_generator.dart | 28 +++ packages/pigeon/pigeons/core_tests.dart | 5 + .../CoreTests.java | 6 + .../DataClassMethodsTest.java | 8 + .../CoreTests.gen.h | 5 + .../ios/RunnerTests/DataClassMethodsTest.m | 7 + .../lib/integration_tests.dart | 7 + .../lib/src/generated/core_tests.gen.dart | 5 + .../test/data_class_methods_test.dart | 7 + .../com/example/test_plugin/CoreTests.gen.kt | 5 + .../test_plugin/DataClassMethodsTest.kt | 8 + .../Sources/test_plugin/CoreTests.gen.swift | 5 + .../RunnerTests/DataClassMethodsTests.swift | 8 + .../test_plugin/linux/pigeon/core_tests.gen.h | 5 + .../linux/test/data_class_methods_test.cc | 7 + .../windows/pigeon/core_tests.gen.h | 5 + .../windows/test/data_class_methods_test.cpp | 7 + packages/pigeon/pubspec.yaml | 2 +- packages/pigeon/test/cpp_generator_test.dart | 46 +++++ packages/pigeon/test/dart_generator_test.dart | 43 ++++ .../pigeon/test/gobject_generator_test.dart | 46 +++++ packages/pigeon/test/java_generator_test.dart | 39 ++++ .../pigeon/test/kotlin_generator_test.dart | 39 ++++ packages/pigeon/test/objc_generator_test.dart | 47 +++++ packages/pigeon/test/pigeon_lib_test.dart | 114 +++++++++++ .../pigeon/test/swift_generator_test.dart | 39 ++++ 39 files changed, 1000 insertions(+), 9 deletions(-) diff --git a/packages/pigeon/CHANGELOG.md b/packages/pigeon/CHANGELOG.md index a87e4e99eee6..57f2116c977a 100644 --- a/packages/pigeon/CHANGELOG.md +++ b/packages/pigeon/CHANGELOG.md @@ -1,3 +1,7 @@ +## 27.2.0 + +* Adds support for sharing constants across platforms. + ## 27.1.0 * [swift] Adds `CaseIterable` conformance to generated enums. diff --git a/packages/pigeon/README.md b/packages/pigeon/README.md index 1029d51f3be9..126c20df46ae 100644 --- a/packages/pigeon/README.md +++ b/packages/pigeon/README.md @@ -1,3 +1,4 @@ + # Pigeon Pigeon is a code generator tool to make communication between Flutter and the @@ -82,6 +83,20 @@ the threading model for handling HostApi methods can be selected with the Host and Flutter APIs now support the ability to provide a unique message channel suffix string to the api to allow for multiple instances to be created and operate in parallel. +### Constants + +Pigeon supports generating top-level constants in the generated files. Constants can be defined at the top level of the Pigeon file: + + +```dart +const String aStringConstant = 'stringConstantValue'; +const int anIntConstant = 42; +const double aDoubleConstant = 3.14; +const bool aBoolConstant = true; +``` + +These constants will be translated into static constants or final variables in the target languages (e.g., `public static final` in Java, `let` in Swift, `const` in Dart, etc.). Only `String`, `int`, `double`, and `bool` constant types are supported. + ## Usage 1) Add pigeon as a `dev_dependency`. diff --git a/packages/pigeon/example/app/pigeons/messages.dart b/packages/pigeon/example/app/pigeons/messages.dart index 585fe650e3e7..48bbc026c534 100644 --- a/packages/pigeon/example/app/pigeons/messages.dart +++ b/packages/pigeon/example/app/pigeons/messages.dart @@ -63,3 +63,10 @@ abstract class MessageFlutterApi { } // #enddocregion flutter-definitions + +// #docregion constants +const String aStringConstant = 'stringConstantValue'; +const int anIntConstant = 42; +const double aDoubleConstant = 3.14; +const bool aBoolConstant = true; +// #enddocregion constants diff --git a/packages/pigeon/lib/src/ast.dart b/packages/pigeon/lib/src/ast.dart index 29e5c34cfd4d..5dd51762954e 100644 --- a/packages/pigeon/lib/src/ast.dart +++ b/packages/pigeon/lib/src/ast.dart @@ -785,6 +785,38 @@ class EnumMember extends Node { } } +/// Represents a constant. +class Constant extends Node { + /// Parametric constructor for [Constant]. + Constant({ + required this.name, + required this.type, + required this.value, + this.offset, + this.documentationComments = const [], + }); + + /// The name of the constant. + final String name; + + /// The type of the constant. + final TypeDeclaration type; + + /// The value of the constant. + final Object value; + + /// The offset in the source file where the constant appears. + final int? offset; + + /// List of documentation comments, separated by line. + final List documentationComments; + + @override + String toString() { + return '(Constant name:$name type:$type value:$value documentationComments:$documentationComments)'; + } +} + /// Top-level node for the AST. class Root extends Node { /// Parametric constructor for [Root]. @@ -792,6 +824,7 @@ class Root extends Node { required this.classes, required this.apis, required this.enums, + this.constants = const [], this.containsHostApi = false, this.containsFlutterApi = false, this.containsProxyApi = false, @@ -800,7 +833,7 @@ class Root extends Node { /// Factory function for generating an empty root, usually used when early errors are encountered. factory Root.makeEmpty() { - return Root(apis: [], classes: [], enums: []); + return Root(apis: [], classes: [], enums: [], constants: []); } /// All the classes contained in the AST. @@ -812,6 +845,9 @@ class Root extends Node { /// All of the enums contained in the AST. List enums; + /// All of the constants contained in the AST. + List constants; + /// Whether the root has any Host API definitions. bool containsHostApi; @@ -833,6 +869,6 @@ class Root extends Node { @override String toString() { - return '(Root classes:$classes apis:$apis enums:$enums)'; + return '(Root classes:$classes apis:$apis enums:$enums constants:$constants)'; } } diff --git a/packages/pigeon/lib/src/cpp/cpp_generator.dart b/packages/pigeon/lib/src/cpp/cpp_generator.dart index 36bc99653a12..156643c2aa47 100644 --- a/packages/pigeon/lib/src/cpp/cpp_generator.dart +++ b/packages/pigeon/lib/src/cpp/cpp_generator.dart @@ -817,6 +817,30 @@ $friendLines '''); } + @override + void writeConstants( + InternalCppOptions generatorOptions, + Root root, + Indent indent, { + required String dartPackageName, + }) { + if (root.constants.isEmpty) { + return; + } + indent.newln(); + for (final Constant constant in root.constants) { + addDocumentationComments(indent, constant.documentationComments, _docCommentSpec); + final String type = constant.type.baseName; + if (type == 'String') { + final String escaped = escapeStringDoubleQuotes(constant.value.toString()); + indent.writeln('inline constexpr const char* ${constant.name} = "$escaped";'); + } else { + final String cppType = _baseCppTypeForBuiltinDartType(constant.type) ?? 'auto'; + indent.writeln('inline constexpr $cppType ${constant.name} = ${constant.value};'); + } + } + } + @override void writeCloseNamespace( InternalCppOptions generatorOptions, diff --git a/packages/pigeon/lib/src/dart/dart_generator.dart b/packages/pigeon/lib/src/dart/dart_generator.dart index ae0614a4a788..2866ddd5a4e2 100644 --- a/packages/pigeon/lib/src/dart/dart_generator.dart +++ b/packages/pigeon/lib/src/dart/dart_generator.dart @@ -186,6 +186,33 @@ class DartGenerator extends StructuredGenerator { indent.writeln("import 'package:meta/meta.dart' show immutable, protected, visibleForTesting;"); } + @override + void writeConstants( + InternalDartOptions generatorOptions, + Root root, + Indent indent, { + required String dartPackageName, + }) { + if (root.constants.isEmpty) { + return; + } + indent.newln(); + for (final Constant constant in root.constants) { + addDocumentationComments(indent, constant.documentationComments, docCommentSpec); + final String formattedValue = _formatValue(constant.type.baseName, constant.value); + indent.writeln('const ${constant.type.baseName} ${constant.name} = $formattedValue;'); + } + } + + String _formatValue(String type, Object value) { + if (type == 'String') { + final String escaped = escapeStringSingleQuotes(value.toString()); + return "'$escaped'"; + } else { + return value.toString(); + } + } + @override void writeEnum( InternalDartOptions generatorOptions, diff --git a/packages/pigeon/lib/src/generator.dart b/packages/pigeon/lib/src/generator.dart index f1e2568749f9..f263bda3f57f 100644 --- a/packages/pigeon/lib/src/generator.dart +++ b/packages/pigeon/lib/src/generator.dart @@ -37,6 +37,8 @@ abstract class StructuredGenerator extends Generator< writeOpenNamespace(generatorOptions, root, indent, dartPackageName: dartPackageName); + writeConstants(generatorOptions, root, indent, dartPackageName: dartPackageName); + writeGeneralUtilities(generatorOptions, root, indent, dartPackageName: dartPackageName); if (root.apis.any((Api api) => api is AstProxyApi)) { @@ -106,6 +108,16 @@ abstract class StructuredGenerator extends Generator< required String dartPackageName, }) {} + /// Writes all constants to [indent]. + /// + /// This method is not required, and does not need to be overridden. + void writeConstants( + T generatorOptions, + Root root, + Indent indent, { + required String dartPackageName, + }) {} + /// Writes all enums to [indent]. /// /// Can be overridden to add extra code before/after enums. diff --git a/packages/pigeon/lib/src/generator_tools.dart b/packages/pigeon/lib/src/generator_tools.dart index 4cc20cfd1cfa..c685bf872245 100644 --- a/packages/pigeon/lib/src/generator_tools.dart +++ b/packages/pigeon/lib/src/generator_tools.dart @@ -15,7 +15,7 @@ import 'generator.dart'; /// The current version of pigeon. /// /// This must match the version in pubspec.yaml. -const String pigeonVersion = '27.1.0'; +const String pigeonVersion = '27.2.0'; /// Default plugin package name. const String defaultPluginPackageName = 'dev.flutter.pigeon'; @@ -871,3 +871,23 @@ bool isCollectionType(TypeDeclaration type) { !type.isProxyApi && (type.baseName.contains('List') || type.baseName == 'Map'); } + +/// Escapes special characters in a string for use in double-quoted string literals. +String escapeStringDoubleQuotes(String value) { + return value + .replaceAll('\\', r'\\') // ignore: use_raw_strings + .replaceAll('"', r'\"') + .replaceAll('\n', r'\n') + .replaceAll('\r', r'\r') + .replaceAll(r'$', r'\$'); +} + +/// Escapes special characters in a string for use in single-quoted string literals. +String escapeStringSingleQuotes(String value) { + return value + .replaceAll('\\', r'\\') // ignore: use_raw_strings + .replaceAll("'", r"\'") + .replaceAll('\n', r'\n') + .replaceAll('\r', r'\r') + .replaceAll(r'$', r'\$'); +} diff --git a/packages/pigeon/lib/src/gobject/gobject_generator.dart b/packages/pigeon/lib/src/gobject/gobject_generator.dart index ce01cfccdfa1..cf643c2b9295 100644 --- a/packages/pigeon/lib/src/gobject/gobject_generator.dart +++ b/packages/pigeon/lib/src/gobject/gobject_generator.dart @@ -198,6 +198,36 @@ class GObjectHeaderGenerator extends StructuredGenerator indent.writeln('G_BEGIN_DECLS'); } + @override + void writeConstants( + InternalGObjectOptions generatorOptions, + Root root, + Indent indent, { + required String dartPackageName, + }) { + if (root.constants.isEmpty) { + return; + } + indent.newln(); + final String module = _getModule(generatorOptions, dartPackageName); + final String moduleScreaming = toScreamingSnakeCase(module); + for (final Constant constant in root.constants) { + addDocumentationComments(indent, constant.documentationComments, _docCommentSpec); + final String constantName = toScreamingSnakeCase(constant.name); + final String type = constant.type.baseName; + final String valueStr; + if (type == 'String') { + final String escaped = escapeStringDoubleQuotes(constant.value.toString()); + valueStr = '"$escaped"'; + } else if (type == 'bool') { + valueStr = (constant.value as bool) ? 'TRUE' : 'FALSE'; + } else { + valueStr = constant.value.toString(); + } + indent.writeln('#define ${moduleScreaming}_$constantName $valueStr'); + } + } + @override void writeEnum( InternalGObjectOptions generatorOptions, diff --git a/packages/pigeon/lib/src/java/java_generator.dart b/packages/pigeon/lib/src/java/java_generator.dart index f37769520866..443e6a9ba0a6 100644 --- a/packages/pigeon/lib/src/java/java_generator.dart +++ b/packages/pigeon/lib/src/java/java_generator.dart @@ -200,9 +200,36 @@ class JavaGenerator extends StructuredGenerator { } indent.writeln('public class ${generatorOptions.className!} {'); indent.inc(); - _writeNumberHelpers(indent); - _writeDeepEquals(indent); - _writeDeepHashCode(indent); + } + + @override + void writeConstants( + InternalJavaOptions generatorOptions, + Root root, + Indent indent, { + required String dartPackageName, + }) { + if (root.constants.isEmpty) { + return; + } + indent.newln(); + for (final Constant constant in root.constants) { + addDocumentationComments(indent, constant.documentationComments, _docCommentSpec); + final String javaType = _javaTypeForBuiltinDartType(constant.type) ?? 'Object'; + final String formattedValue = _formatJavaValue(constant.type.baseName, constant.value); + indent.writeln('public static final $javaType ${constant.name} = $formattedValue;'); + } + } + + String _formatJavaValue(String type, Object value) { + if (type == 'String') { + final String escaped = escapeStringDoubleQuotes(value.toString()); + return '"$escaped"'; + } else if (type == 'int') { + return '${value}L'; + } else { + return value.toString(); + } } @override @@ -1322,6 +1349,10 @@ protected static ArrayList wrapError(@NonNull Throwable exception) { Indent indent, { required String dartPackageName, }) { + indent.newln(); + _writeNumberHelpers(indent); + _writeDeepEquals(indent); + _writeDeepHashCode(indent); indent.newln(); _writeErrorClass(indent); if (root.containsHostApi) { diff --git a/packages/pigeon/lib/src/kotlin/kotlin_generator.dart b/packages/pigeon/lib/src/kotlin/kotlin_generator.dart index 91b605fc78fe..bfbda2201074 100644 --- a/packages/pigeon/lib/src/kotlin/kotlin_generator.dart +++ b/packages/pigeon/lib/src/kotlin/kotlin_generator.dart @@ -239,6 +239,36 @@ class KotlinGenerator extends StructuredGenerator { } } + @override + void writeConstants( + InternalKotlinOptions generatorOptions, + Root root, + Indent indent, { + required String dartPackageName, + }) { + if (root.constants.isEmpty) { + return; + } + indent.newln(); + for (final Constant constant in root.constants) { + addDocumentationComments(indent, constant.documentationComments, _docCommentSpec); + final String kotlinType = _kotlinTypeForBuiltinDartType(constant.type) ?? 'Any'; + final String formattedValue = _formatKotlinValue(constant.type.baseName, constant.value); + indent.writeln('const val ${constant.name}: $kotlinType = $formattedValue'); + } + } + + String _formatKotlinValue(String type, Object value) { + if (type == 'String') { + final String escaped = escapeStringDoubleQuotes(value.toString()); + return '"$escaped"'; + } else if (type == 'int') { + return '${value}L'; + } else { + return value.toString(); + } + } + @override void writeEnum( InternalKotlinOptions generatorOptions, diff --git a/packages/pigeon/lib/src/objc/objc_generator.dart b/packages/pigeon/lib/src/objc/objc_generator.dart index 9751b5f8e9f3..689eaae57c4c 100644 --- a/packages/pigeon/lib/src/objc/objc_generator.dart +++ b/packages/pigeon/lib/src/objc/objc_generator.dart @@ -206,6 +206,34 @@ class ObjcHeaderGenerator extends StructuredGenerator { indent.writeln('NS_ASSUME_NONNULL_BEGIN'); } + @override + void writeConstants( + InternalObjcOptions generatorOptions, + Root root, + Indent indent, { + required String dartPackageName, + }) { + if (root.constants.isEmpty) { + return; + } + indent.newln(); + for (final Constant constant in root.constants) { + addDocumentationComments(indent, constant.documentationComments, _docCommentSpec); + final String constantName = _className(generatorOptions.prefix, constant.name); + final String type = constant.type.baseName; + if (type == 'String') { + final String escaped = escapeStringDoubleQuotes(constant.value.toString()); + indent.writeln('static NSString *const $constantName = @"$escaped";'); + } else if (type == 'bool') { + final boolVal = (constant.value as bool) ? 'YES' : 'NO'; + indent.writeln('static const BOOL $constantName = $boolVal;'); + } else { + final _ObjcType objcType = _objcTypeForDartType(generatorOptions.prefix, constant.type); + indent.writeln('static const $objcType $constantName = ${constant.value};'); + } + } + } + @override void writeEnum( InternalObjcOptions generatorOptions, @@ -467,8 +495,6 @@ class ObjcSourceGenerator extends StructuredGenerator { indent.writeln('@import Flutter;'); indent.writeln('#endif'); indent.newln(); - _writeDeepEquals(indent); - _writeDeepHash(indent); } @override @@ -1005,6 +1031,9 @@ if (self.wrapped == nil) { Indent indent, { required String dartPackageName, }) { + _writeDeepEquals(indent); + _writeDeepHash(indent); + indent.newln(); if (root.containsHostApi) { _writeWrapError(indent); indent.newln(); diff --git a/packages/pigeon/lib/src/pigeon_lib_internal.dart b/packages/pigeon/lib/src/pigeon_lib_internal.dart index b9607e12f528..9267f070d469 100644 --- a/packages/pigeon/lib/src/pigeon_lib_internal.dart +++ b/packages/pigeon/lib/src/pigeon_lib_internal.dart @@ -1094,6 +1094,7 @@ class RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { final List _apis = []; final List _enums = []; final List _classes = []; + final List _constants = []; final List _errors = []; /// Input file location. @@ -1157,6 +1158,7 @@ class RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { apis: _apis, classes: _classes, enums: referencedEnums, + constants: _constants, containsHostApi: containsHostApi, containsFlutterApi: containsFlutterApi, containsProxyApi: containsProxyApi, @@ -1216,6 +1218,66 @@ class RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { final List validateErrors = _validateAst(completeRoot, source); totalErrors.addAll(validateErrors); + for (final Constant constant in _constants) { + if (constant.type.baseName != 'String' && + constant.type.baseName != 'int' && + constant.type.baseName != 'double' && + constant.type.baseName != 'bool') { + totalErrors.add( + Error( + message: + 'Unsupported constant type: "${constant.type.baseName}". Only String, int, double, and bool are supported.', + lineNumber: constant.offset != null + ? calculateLineNumber(source, constant.offset!) + : null, + ), + ); + } + if (constant.type.baseName == 'String' && constant.value is! String) { + totalErrors.add( + Error( + message: + 'Constant "${constant.name}" type is String but value is ${constant.value.runtimeType}.', + lineNumber: constant.offset != null + ? calculateLineNumber(source, constant.offset!) + : null, + ), + ); + } else if (constant.type.baseName == 'int' && constant.value is! int) { + totalErrors.add( + Error( + message: + 'Constant "${constant.name}" type is int but value is ${constant.value.runtimeType}.', + lineNumber: constant.offset != null + ? calculateLineNumber(source, constant.offset!) + : null, + ), + ); + } else if (constant.type.baseName == 'double' && + constant.value is! double && + constant.value is! int) { + totalErrors.add( + Error( + message: + 'Constant "${constant.name}" type is double but value is ${constant.value.runtimeType}.', + lineNumber: constant.offset != null + ? calculateLineNumber(source, constant.offset!) + : null, + ), + ); + } else if (constant.type.baseName == 'bool' && constant.value is! bool) { + totalErrors.add( + Error( + message: + 'Constant "${constant.name}" type is bool but value is ${constant.value.runtimeType}.', + lineNumber: constant.offset != null + ? calculateLineNumber(source, constant.offset!) + : null, + ), + ); + } + } + return ParseResults( root: totalErrors.isEmpty ? completeRoot @@ -1354,6 +1416,129 @@ class RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { return null; } + @override + Object? visitTopLevelVariableDeclaration(dart_ast.TopLevelVariableDeclaration node) { + if (node.variables.isConst) { + final dart_ast.TypeAnnotation? typeAnnotation = node.variables.type; + if (typeAnnotation == null) { + _errors.add( + Error( + message: 'Top-level constants must have an explicit type annotation.', + lineNumber: calculateLineNumber(source, node.offset), + ), + ); + return null; + } + if (typeAnnotation is! dart_ast.NamedType) { + _errors.add( + Error( + message: 'Top-level constants must have a named type annotation.', + lineNumber: calculateLineNumber(source, node.offset), + ), + ); + return null; + } + for (final dart_ast.VariableDeclaration variable in node.variables.variables) { + final dart_ast.Expression? initializer = variable.initializer; + if (initializer == null) { + _errors.add( + Error( + message: 'Top-level constant "${variable.name.lexeme}" must have an initializer.', + lineNumber: calculateLineNumber(source, variable.offset), + ), + ); + continue; + } + + final Object? value = _evaluateExpression(initializer); + if (value == null) { + continue; + } + + final type = TypeDeclaration( + baseName: _getNamedTypeQualifiedName(typeAnnotation), + isNullable: typeAnnotation.question != null, + typeArguments: _typeAnnotationsToTypeArguments(typeAnnotation.typeArguments), + ); + + _constants.add( + Constant( + name: variable.name.lexeme, + type: type, + value: value, + offset: variable.offset, + documentationComments: _documentationCommentsParser(node.documentationComment?.tokens), + ), + ); + } + } + node.visitChildren(this); + return null; + } + + Object? _evaluateExpression(dart_ast.Expression expression) { + if (expression is dart_ast.SimpleStringLiteral) { + return expression.value; + } else if (expression is dart_ast.IntegerLiteral) { + return expression.value!; + } else if (expression is dart_ast.DoubleLiteral) { + return expression.value; + } else if (expression is dart_ast.BooleanLiteral) { + return expression.value; + } else if (expression is dart_ast.PrefixExpression) { + final Object? operandValue = _evaluateExpression(expression.operand); + if (operandValue == null) { + return null; + } + final String operator = expression.operator.lexeme; + if (operator == '-') { + if (operandValue is int) { + return -operandValue; + } else if (operandValue is double) { + return -operandValue; + } + } else if (operator == '!') { + if (operandValue is bool) { + return !operandValue; + } + } + _errors.add( + Error( + message: 'Unsupported prefix operator "$operator" on type "${operandValue.runtimeType}".', + lineNumber: calculateLineNumber(source, expression.offset), + ), + ); + return null; + } else if (expression is dart_ast.AdjacentStrings) { + final buffer = StringBuffer(); + for (final dart_ast.StringLiteral literal in expression.strings) { + final Object? val = _evaluateExpression(literal); + if (val is! String) { + return null; + } + buffer.write(val); + } + return buffer.toString(); + } else if (expression is dart_ast.StringInterpolation) { + _errors.add( + Error( + message: 'String interpolation is not supported in Pigeon constants.', + lineNumber: calculateLineNumber(source, expression.offset), + ), + ); + return null; + } else { + _errors.add( + Error( + message: + 'Unsupported expression type ${expression.runtimeType} for constant initializer.', + lineNumber: calculateLineNumber(source, expression.offset), + ), + ); + return null; + } + } + @override Object? visitAnnotation(dart_ast.Annotation node) { if (node.name.name == 'ConfigurePigeon') { diff --git a/packages/pigeon/lib/src/swift/swift_generator.dart b/packages/pigeon/lib/src/swift/swift_generator.dart index 248ce1a18abc..200460ff145d 100644 --- a/packages/pigeon/lib/src/swift/swift_generator.dart +++ b/packages/pigeon/lib/src/swift/swift_generator.dart @@ -223,6 +223,34 @@ class SwiftGenerator extends StructuredGenerator { #endif'''); } + @override + void writeConstants( + InternalSwiftOptions generatorOptions, + Root root, + Indent indent, { + required String dartPackageName, + }) { + if (root.constants.isEmpty) { + return; + } + indent.newln(); + for (final Constant constant in root.constants) { + addDocumentationComments(indent, constant.documentationComments, _docCommentSpec); + final String swiftType = _swiftTypeForDartType(constant.type); + final String formattedValue = _formatSwiftValue(constant.type.baseName, constant.value); + indent.writeln('public let ${constant.name}: $swiftType = $formattedValue'); + } + } + + String _formatSwiftValue(String type, Object value) { + if (type == 'String') { + final String escaped = escapeStringDoubleQuotes(value.toString()); + return '"$escaped"'; + } else { + return value.toString(); + } + } + @override void writeEnum( InternalSwiftOptions generatorOptions, diff --git a/packages/pigeon/pigeons/core_tests.dart b/packages/pigeon/pigeons/core_tests.dart index e93cecf067c2..de1458153277 100644 --- a/packages/pigeon/pigeons/core_tests.dart +++ b/packages/pigeon/pigeons/core_tests.dart @@ -4,6 +4,11 @@ import 'package:pigeon/pigeon.dart'; +const String aStringConstant = 'stringConstantValue'; +const int anIntConstant = 42; +const double aDoubleConstant = 3.14; +const bool aBoolConstant = true; + enum AnEnum { one, two, three, fortyTwo, fourHundredTwentyTwo } // Enums require special logic, having multiple ensures that the logic can be diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/main/java/com/example/alternate_language_test_plugin/CoreTests.java b/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/main/java/com/example/alternate_language_test_plugin/CoreTests.java index da1de3d6a4da..8a55eb38651e 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/main/java/com/example/alternate_language_test_plugin/CoreTests.java +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/main/java/com/example/alternate_language_test_plugin/CoreTests.java @@ -30,6 +30,12 @@ /** Generated class from Pigeon. */ @SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression", "serial"}) public class CoreTests { + + public static final String aStringConstant = "stringConstantValue"; + public static final Long anIntConstant = 42L; + public static final Double aDoubleConstant = 3.14; + public static final Boolean aBoolConstant = true; + static boolean pigeonDoubleEquals(double a, double b) { // Normalize -0.0 to 0.0 and handle NaN equality. return (a == 0.0 ? 0.0 : a) == (b == 0.0 ? 0.0 : b) || (Double.isNaN(a) && Double.isNaN(b)); diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/test/java/com/example/alternate_language_test_plugin/DataClassMethodsTest.java b/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/test/java/com/example/alternate_language_test_plugin/DataClassMethodsTest.java index 7b0c28355014..bce312f3e1c6 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/test/java/com/example/alternate_language_test_plugin/DataClassMethodsTest.java +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/test/java/com/example/alternate_language_test_plugin/DataClassMethodsTest.java @@ -405,4 +405,12 @@ public void nestedZeroArrayEquality() { assertEquals(a, b); assertEquals(a.hashCode(), b.hashCode()); } + + @Test + public void testConstants() { + assertEquals("stringConstantValue", CoreTests.aStringConstant); + assertEquals(Long.valueOf(42L), CoreTests.anIntConstant); + assertEquals(Double.valueOf(3.14), CoreTests.aDoubleConstant); + assertEquals(Boolean.TRUE, CoreTests.aBoolConstant); + } } diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/darwin/alternate_language_test_plugin/Sources/alternate_language_test_plugin/include/alternate_language_test_plugin/CoreTests.gen.h b/packages/pigeon/platform_tests/alternate_language_test_plugin/darwin/alternate_language_test_plugin/Sources/alternate_language_test_plugin/include/alternate_language_test_plugin/CoreTests.gen.h index 03be8ebb358d..38108546c930 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/darwin/alternate_language_test_plugin/Sources/alternate_language_test_plugin/include/alternate_language_test_plugin/CoreTests.gen.h +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/darwin/alternate_language_test_plugin/Sources/alternate_language_test_plugin/include/alternate_language_test_plugin/CoreTests.gen.h @@ -14,6 +14,11 @@ NS_ASSUME_NONNULL_BEGIN +static NSString *const FLTaStringConstant = @"stringConstantValue"; +static const NSInteger FLTanIntConstant = 42; +static const double FLTaDoubleConstant = 3.14; +static const BOOL FLTaBoolConstant = YES; + typedef NS_ENUM(NSUInteger, FLTAnEnum) { FLTAnEnumOne = 0, FLTAnEnumTwo = 1, diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/DataClassMethodsTest.m b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/DataClassMethodsTest.m index dbe11c1a5aa7..82817970d4fb 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/DataClassMethodsTest.m +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/DataClassMethodsTest.m @@ -253,4 +253,11 @@ - (void)testNSNullPropertyEquality { XCTAssertEqual(a.hash, b.hash); } +- (void)testConstants { + XCTAssertEqualObjects(FLTaStringConstant, @"stringConstantValue"); + XCTAssertEqual(FLTanIntConstant, 42); + XCTAssertEqual(FLTaDoubleConstant, 3.14); + XCTAssertEqual(FLTaBoolConstant, YES); +} + @end diff --git a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/integration_tests.dart b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/integration_tests.dart index 6928aae9e54e..fb6fa69dc7c2 100644 --- a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/integration_tests.dart +++ b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/integration_tests.dart @@ -2795,6 +2795,13 @@ void runPigeonIntegrationTests(TargetGenerator targetGenerator) { await completer1.future; await completer2.future; }, skip: !eventChannelSupported.contains(targetGenerator)); + + testWidgets('constants are generated correctly', (WidgetTester _) async { + expect(aStringConstant, 'stringConstantValue'); + expect(anIntConstant, 42); + expect(aDoubleConstant, 3.14); + expect(aBoolConstant, true); + }); } class _FlutterApiTestImplementation implements FlutterIntegrationCoreApi { diff --git a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/core_tests.gen.dart b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/core_tests.gen.dart index c1164b5c7892..7e55f9809435 100644 --- a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/core_tests.gen.dart +++ b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/core_tests.gen.dart @@ -13,6 +13,11 @@ import 'dart:typed_data' show Float64List, Int32List, Int64List; import 'package:flutter/services.dart'; import 'package:meta/meta.dart' show immutable, protected, visibleForTesting; +const String aStringConstant = 'stringConstantValue'; +const int anIntConstant = 42; +const double aDoubleConstant = 3.14; +const bool aBoolConstant = true; + Object? _extractReplyValueOrThrow( List? replyList, String channelName, { diff --git a/packages/pigeon/platform_tests/shared_test_plugin_code/test/data_class_methods_test.dart b/packages/pigeon/platform_tests/shared_test_plugin_code/test/data_class_methods_test.dart index 33407ef7fc89..a64c03f80536 100644 --- a/packages/pigeon/platform_tests/shared_test_plugin_code/test/data_class_methods_test.dart +++ b/packages/pigeon/platform_tests/shared_test_plugin_code/test/data_class_methods_test.dart @@ -199,5 +199,12 @@ void main() { 'AllTypes(aBool: true, anInt: 42, anInt64: 3000000000, aDouble: 3.14159, aByteArray: [1, 2, 3], a4ByteArray: [4, 5, 6], a8ByteArray: [7, 8, 9], aFloatArray: [2.71828, 3.14159], anEnum: AnEnum.fortyTwo, anotherEnum: AnotherEnum.justInCase, aString: Hello host!, anObject: 1, list: [Thing 1, 2, true, 3.14, null], stringList: [Thing 1, 2, true, 3.14], intList: [1, 2, 3, 4], doubleList: [1.0, 2.99999, 3.0, 3.14], boolList: [true, false, true, false], enumList: [AnEnum.one, AnEnum.two, AnEnum.three, AnEnum.fortyTwo, AnEnum.fourHundredTwentyTwo], objectList: [Thing 1, 2, true, 3.14], listList: [[Thing 1, 2, true, 3.14], [Thing 1, 2, true, 3.14], [1, 2, 3, 4], [1.0, 2.99999, 3.0, 3.14], [true, false, true, false], [AnEnum.one, AnEnum.two, AnEnum.three, AnEnum.fortyTwo, AnEnum.fourHundredTwentyTwo]], mapList: [{a: 1, b: 2.0, c: three, d: false}, {a: 1, b: 2.0, c: three, d: false}, {0.0: 0.0, 1.1: 2.0, 3.0: 0.3, -0.4: -0.2}, {0: 0, 1: 1, 2: 3, 4: -1}, {0: true, 1: false, 2: true}, {AnEnum.one: AnEnum.one, AnEnum.two: AnEnum.two, AnEnum.three: AnEnum.three, AnEnum.fortyTwo: AnEnum.fortyTwo}], map: {a: 1, b: 2.0, c: three, d: false}, stringMap: {a: 1, b: 2.0, c: three, d: false}, intMap: {0: 0, 1: 1, 2: 3, 4: -1}, enumMap: {AnEnum.one: AnEnum.one, AnEnum.two: AnEnum.two, AnEnum.three: AnEnum.three, AnEnum.fortyTwo: AnEnum.fortyTwo}, objectMap: {a: 1, b: 2.0, c: three, d: false}, listMap: {0: [Thing 1, 2, true, 3.14], 1: [Thing 1, 2, true, 3.14], 2: [1.0, 2.99999, 3.0, 3.14], 4: [1, 2, 3, 4], 5: [true, false, true, false], 6: [AnEnum.one, AnEnum.two, AnEnum.three, AnEnum.fortyTwo, AnEnum.fourHundredTwentyTwo]}, mapMap: {0: {a: 1, b: 2.0, c: three, d: false}, 1: {a: 1, b: 2.0, c: three, d: false}, 2: {0.0: 0.0, 1.1: 2.0, 3.0: 0.3, -0.4: -0.2}, 4: {0: 0, 1: 1, 2: 3, 4: -1}, 5: {0: true, 1: false, 2: true}, 6: {AnEnum.one: AnEnum.one, AnEnum.two: AnEnum.two, AnEnum.three: AnEnum.three, AnEnum.fortyTwo: AnEnum.fortyTwo}})', ); }); + + test('constants', () { + expect(aStringConstant, 'stringConstantValue'); + expect(anIntConstant, 42); + expect(aDoubleConstant, 3.14); + expect(aBoolConstant, true); + }); }); } diff --git a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/CoreTests.gen.kt b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/CoreTests.gen.kt index a22c40a3c5f3..796a724b670f 100644 --- a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/CoreTests.gen.kt +++ b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/CoreTests.gen.kt @@ -16,6 +16,11 @@ import io.flutter.plugin.common.StandardMessageCodec import java.io.ByteArrayOutputStream import java.nio.ByteBuffer +const val aStringConstant: String = "stringConstantValue" +const val anIntConstant: Long = 42L +const val aDoubleConstant: Double = 3.14 +const val aBoolConstant: Boolean = true + private object CoreTestsPigeonUtils { fun createConnectionError(channelName: String): FlutterError { diff --git a/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/DataClassMethodsTest.kt b/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/DataClassMethodsTest.kt index ab9a69d91208..81ce0b3adf1d 100644 --- a/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/DataClassMethodsTest.kt +++ b/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/DataClassMethodsTest.kt @@ -303,4 +303,12 @@ internal class DataClassMethodsTest { "AllNullableTypes(aNullableBool=false, aNullableInt=1234, aNullableInt64=null, aNullableDouble=2.0, aNullableByteArray=[1, 2, 3, 4], aNullable4ByteArray=[1, 2, 3, 4], aNullable8ByteArray=[1, 2, 3, 4], aNullableFloatArray=[0.5, 0.25, 1.5, 1.25], aNullableEnum=TWO, anotherNullableEnum=JUST_IN_CASE, aNullableString=hello, aNullableObject=0, allNullableTypes=null, list=[1, 2, 3], stringList=[string, another one], intList=[1, 2], doubleList=[1.1, 2.2], boolList=[true, false], enumList=[ONE, TWO], objectList=[1, 2, 3], listList=[[string, another one], [string, another one]], mapList=[{hello=1234}, {hello=1234}], recursiveClassList=null, map={hello=1234}, stringMap={hello=you}, intMap={1=0}, enumMap={ONE=FORTY_TWO, TWO=FOUR_HUNDRED_TWENTY_TWO}, objectMap={hello=1234}, listMap={1=[string, another one]}, mapMap={1={}}, recursiveClassMap=null)", everything.toString()) } + + @Test + fun testConstants() { + assertEquals("stringConstantValue", aStringConstant) + assertEquals(42L, anIntConstant) + assertEquals(3.14, aDoubleConstant, 0.0) + assertEquals(true, aBoolConstant) + } } diff --git a/packages/pigeon/platform_tests/test_plugin/darwin/test_plugin/Sources/test_plugin/CoreTests.gen.swift b/packages/pigeon/platform_tests/test_plugin/darwin/test_plugin/Sources/test_plugin/CoreTests.gen.swift index 3269407620db..c290eae38f0b 100644 --- a/packages/pigeon/platform_tests/test_plugin/darwin/test_plugin/Sources/test_plugin/CoreTests.gen.swift +++ b/packages/pigeon/platform_tests/test_plugin/darwin/test_plugin/Sources/test_plugin/CoreTests.gen.swift @@ -15,6 +15,11 @@ import Foundation #error("Unsupported platform.") #endif +public let aStringConstant: String = "stringConstantValue" +public let anIntConstant: Int64 = 42 +public let aDoubleConstant: Double = 3.14 +public let aBoolConstant: Bool = true + /// Error class for passing custom error details to Dart side. final class PigeonError: Error { let code: String diff --git a/packages/pigeon/platform_tests/test_plugin/example/ios/RunnerTests/DataClassMethodsTests.swift b/packages/pigeon/platform_tests/test_plugin/example/ios/RunnerTests/DataClassMethodsTests.swift index 69360f095190..0b9a738663ad 100644 --- a/packages/pigeon/platform_tests/test_plugin/example/ios/RunnerTests/DataClassMethodsTests.swift +++ b/packages/pigeon/platform_tests/test_plugin/example/ios/RunnerTests/DataClassMethodsTests.swift @@ -383,4 +383,12 @@ struct DataClassMethodsTests { #expect(hashA == hashB) } + + @Test + func testConstants() { + #expect(aStringConstant == "stringConstantValue") + #expect(anIntConstant == 42) + #expect(aDoubleConstant == 3.14) + #expect(aBoolConstant == true) + } } diff --git a/packages/pigeon/platform_tests/test_plugin/linux/pigeon/core_tests.gen.h b/packages/pigeon/platform_tests/test_plugin/linux/pigeon/core_tests.gen.h index e5d97e289756..37d2c850e062 100644 --- a/packages/pigeon/platform_tests/test_plugin/linux/pigeon/core_tests.gen.h +++ b/packages/pigeon/platform_tests/test_plugin/linux/pigeon/core_tests.gen.h @@ -12,6 +12,11 @@ G_BEGIN_DECLS +#define CORE_TESTS_PIGEON_TEST_A_STRING_CONSTANT "stringConstantValue" +#define CORE_TESTS_PIGEON_TEST_AN_INT_CONSTANT 42 +#define CORE_TESTS_PIGEON_TEST_A_DOUBLE_CONSTANT 3.14 +#define CORE_TESTS_PIGEON_TEST_A_BOOL_CONSTANT TRUE + /** * CoreTestsPigeonTestAnEnum: * PIGEON_INTEGRATION_TESTS_AN_ENUM_ONE: diff --git a/packages/pigeon/platform_tests/test_plugin/linux/test/data_class_methods_test.cc b/packages/pigeon/platform_tests/test_plugin/linux/test/data_class_methods_test.cc index 0346da708dec..25f839e4a9bf 100644 --- a/packages/pigeon/platform_tests/test_plugin/linux/test/data_class_methods_test.cc +++ b/packages/pigeon/platform_tests/test_plugin/linux/test/data_class_methods_test.cc @@ -303,3 +303,10 @@ TEST(Equality, ToStringSnapshot) { g_free(str); } + +TEST(Constants, VerifyConstants) { + EXPECT_STREQ(CORE_TESTS_PIGEON_TEST_A_STRING_CONSTANT, "stringConstantValue"); + EXPECT_EQ(CORE_TESTS_PIGEON_TEST_AN_INT_CONSTANT, 42); + EXPECT_DOUBLE_EQ(CORE_TESTS_PIGEON_TEST_A_DOUBLE_CONSTANT, 3.14); + EXPECT_TRUE(CORE_TESTS_PIGEON_TEST_A_BOOL_CONSTANT); +} diff --git a/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.h b/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.h index 6778e1943213..722328ea0649 100644 --- a/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.h +++ b/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.h @@ -23,6 +23,11 @@ class CoreTestsTest; // Generated class from Pigeon. +inline constexpr const char* aStringConstant = "stringConstantValue"; +inline constexpr int64_t anIntConstant = 42; +inline constexpr double aDoubleConstant = 3.14; +inline constexpr bool aBoolConstant = true; + class FlutterError { public: explicit FlutterError(const std::string& code) : code_(code) {} diff --git a/packages/pigeon/platform_tests/test_plugin/windows/test/data_class_methods_test.cpp b/packages/pigeon/platform_tests/test_plugin/windows/test/data_class_methods_test.cpp index dc1f6ad3f809..53b0b91720cc 100644 --- a/packages/pigeon/platform_tests/test_plugin/windows/test/data_class_methods_test.cpp +++ b/packages/pigeon/platform_tests/test_plugin/windows/test/data_class_methods_test.cpp @@ -153,5 +153,12 @@ TEST(SerializationTests, StreamOutputFullSnapshot) { "map_map: null, recursive_class_map: null)"); } +TEST(ConstantTests, VerifyConstants) { + EXPECT_EQ(std::string(aStringConstant), "stringConstantValue"); + EXPECT_EQ(anIntConstant, 42); + EXPECT_DOUBLE_EQ(aDoubleConstant, 3.14); + EXPECT_EQ(aBoolConstant, true); +} + } // namespace test } // namespace test_plugin diff --git a/packages/pigeon/pubspec.yaml b/packages/pigeon/pubspec.yaml index 65afaca2890f..8103bc0b2e72 100644 --- a/packages/pigeon/pubspec.yaml +++ b/packages/pigeon/pubspec.yaml @@ -2,7 +2,7 @@ name: pigeon description: Code generator tool to make communication between Flutter and the host platform type-safe and easier. repository: https://github.com/flutter/packages/tree/main/packages/pigeon issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+pigeon%22 -version: 27.1.0 # This must match the version in lib/src/generator_tools.dart +version: 27.2.0 # This must match the version in lib/src/generator_tools.dart environment: sdk: ^3.10.0 diff --git a/packages/pigeon/test/cpp_generator_test.dart b/packages/pigeon/test/cpp_generator_test.dart index fa65936598ca..cab245024fcb 100644 --- a/packages/pigeon/test/cpp_generator_test.dart +++ b/packages/pigeon/test/cpp_generator_test.dart @@ -2388,4 +2388,50 @@ void main() { expect(code, contains('size_t Input::Hash() const {')); } }); + + test('gen constants', () { + final root = Root( + apis: [], + classes: [], + enums: [], + constants: [ + Constant( + name: 'stringConst', + type: const TypeDeclaration(baseName: 'String', isNullable: false), + value: 'hello', + ), + Constant( + name: 'intConst', + type: const TypeDeclaration(baseName: 'int', isNullable: false), + value: 42, + ), + Constant( + name: 'doubleConst', + type: const TypeDeclaration(baseName: 'double', isNullable: false), + value: 3.14, + ), + Constant( + name: 'boolConst', + type: const TypeDeclaration(baseName: 'bool', isNullable: false), + value: true, + ), + ], + ); + final sink = StringBuffer(); + const generator = CppGenerator(); + final generatorOptions = OutputFileOptions( + fileType: FileType.header, + languageOptions: const InternalCppOptions( + headerIncludePath: 'foo.h', + cppHeaderOut: '', + cppSourceOut: '', + ), + ); + generator.generate(generatorOptions, root, sink, dartPackageName: DEFAULT_PACKAGE_NAME); + final code = sink.toString(); + expect(code, contains('inline constexpr const char* stringConst = "hello";')); + expect(code, contains('inline constexpr int64_t intConst = 42;')); + expect(code, contains('inline constexpr double doubleConst = 3.14;')); + expect(code, contains('inline constexpr bool boolConst = true;')); + }); } diff --git a/packages/pigeon/test/dart_generator_test.dart b/packages/pigeon/test/dart_generator_test.dart index 695b9a97c8c5..d740ef2d731b 100644 --- a/packages/pigeon/test/dart_generator_test.dart +++ b/packages/pigeon/test/dart_generator_test.dart @@ -2027,4 +2027,47 @@ name: foobar expect(code, contains('String toString() {')); expect(code, contains(r"return 'Foobar(field1: $field1)';")); }); + + test('gen constants', () { + final root = Root( + apis: [], + classes: [], + enums: [], + constants: [ + Constant( + name: 'stringConst', + type: const TypeDeclaration(baseName: 'String', isNullable: false), + value: 'hello', + ), + Constant( + name: 'intConst', + type: const TypeDeclaration(baseName: 'int', isNullable: false), + value: 42, + ), + Constant( + name: 'doubleConst', + type: const TypeDeclaration(baseName: 'double', isNullable: false), + value: 3.14, + ), + Constant( + name: 'boolConst', + type: const TypeDeclaration(baseName: 'bool', isNullable: false), + value: true, + ), + ], + ); + final sink = StringBuffer(); + const generator = DartGenerator(); + generator.generate( + const InternalDartOptions(ignoreLints: false), + root, + sink, + dartPackageName: DEFAULT_PACKAGE_NAME, + ); + final code = sink.toString(); + expect(code, contains("const String stringConst = 'hello';")); + expect(code, contains('const int intConst = 42;')); + expect(code, contains('const double doubleConst = 3.14;')); + expect(code, contains('const bool boolConst = true;')); + }); } diff --git a/packages/pigeon/test/gobject_generator_test.dart b/packages/pigeon/test/gobject_generator_test.dart index 23e4f77f1244..566892ed5246 100644 --- a/packages/pigeon/test/gobject_generator_test.dart +++ b/packages/pigeon/test/gobject_generator_test.dart @@ -936,4 +936,50 @@ void main() { expect(code, contains('gchar* test_package_input_to_string(')); expect(code, contains('g_string_new("Input(");')); }); + + test('gen constants', () { + final root = Root( + apis: [], + classes: [], + enums: [], + constants: [ + Constant( + name: 'stringConst', + type: const TypeDeclaration(baseName: 'String', isNullable: false), + value: 'hello', + ), + Constant( + name: 'intConst', + type: const TypeDeclaration(baseName: 'int', isNullable: false), + value: 42, + ), + Constant( + name: 'doubleConst', + type: const TypeDeclaration(baseName: 'double', isNullable: false), + value: 3.14, + ), + Constant( + name: 'boolConst', + type: const TypeDeclaration(baseName: 'bool', isNullable: false), + value: true, + ), + ], + ); + final sink = StringBuffer(); + const generator = GObjectGenerator(); + final generatorOptions = OutputFileOptions( + fileType: FileType.header, + languageOptions: const InternalGObjectOptions( + headerIncludePath: 'foo.h', + gobjectHeaderOut: '', + gobjectSourceOut: '', + ), + ); + generator.generate(generatorOptions, root, sink, dartPackageName: DEFAULT_PACKAGE_NAME); + final code = sink.toString(); + expect(code, contains('#define TEST_PACKAGE_STRING_CONST "hello"')); + expect(code, contains('#define TEST_PACKAGE_INT_CONST 42')); + expect(code, contains('#define TEST_PACKAGE_DOUBLE_CONST 3.14')); + expect(code, contains('#define TEST_PACKAGE_BOOL_CONST TRUE')); + }); } diff --git a/packages/pigeon/test/java_generator_test.dart b/packages/pigeon/test/java_generator_test.dart index 6a93704be7a5..be307a6bb126 100644 --- a/packages/pigeon/test/java_generator_test.dart +++ b/packages/pigeon/test/java_generator_test.dart @@ -1582,4 +1582,43 @@ void main() { expect(code, contains('public String toString() {')); expect(code, contains('return "Foobar{" + "field1=" + field1 + "}";')); }); + + test('gen constants', () { + final root = Root( + apis: [], + classes: [], + enums: [], + constants: [ + Constant( + name: 'stringConst', + type: const TypeDeclaration(baseName: 'String', isNullable: false), + value: 'hello', + ), + Constant( + name: 'intConst', + type: const TypeDeclaration(baseName: 'int', isNullable: false), + value: 42, + ), + Constant( + name: 'doubleConst', + type: const TypeDeclaration(baseName: 'double', isNullable: false), + value: 3.14, + ), + Constant( + name: 'boolConst', + type: const TypeDeclaration(baseName: 'bool', isNullable: false), + value: true, + ), + ], + ); + final sink = StringBuffer(); + const javaOptions = InternalJavaOptions(className: 'Messages', javaOut: ''); + const generator = JavaGenerator(); + generator.generate(javaOptions, root, sink, dartPackageName: DEFAULT_PACKAGE_NAME); + final code = sink.toString(); + expect(code, contains('public static final String stringConst = "hello";')); + expect(code, contains('public static final Long intConst = 42L;')); + expect(code, contains('public static final Double doubleConst = 3.14;')); + expect(code, contains('public static final Boolean boolConst = true;')); + }); } diff --git a/packages/pigeon/test/kotlin_generator_test.dart b/packages/pigeon/test/kotlin_generator_test.dart index 5164c203860e..0610c233414b 100644 --- a/packages/pigeon/test/kotlin_generator_test.dart +++ b/packages/pigeon/test/kotlin_generator_test.dart @@ -1827,4 +1827,43 @@ void main() { expect(code, contains('override fun toString(): String {')); expect(code, contains(r'return "Foobar(field1=$field1, field2=$field2)"')); }); + + test('gen constants', () { + final root = Root( + apis: [], + classes: [], + enums: [], + constants: [ + Constant( + name: 'stringConst', + type: const TypeDeclaration(baseName: 'String', isNullable: false), + value: 'hello', + ), + Constant( + name: 'intConst', + type: const TypeDeclaration(baseName: 'int', isNullable: false), + value: 42, + ), + Constant( + name: 'doubleConst', + type: const TypeDeclaration(baseName: 'double', isNullable: false), + value: 3.14, + ), + Constant( + name: 'boolConst', + type: const TypeDeclaration(baseName: 'bool', isNullable: false), + value: true, + ), + ], + ); + final sink = StringBuffer(); + const kotlinOptions = InternalKotlinOptions(kotlinOut: ''); + const generator = KotlinGenerator(); + generator.generate(kotlinOptions, root, sink, dartPackageName: DEFAULT_PACKAGE_NAME); + final code = sink.toString(); + expect(code, contains('const val stringConst: String = "hello"')); + expect(code, contains('const val intConst: Long = 42L')); + expect(code, contains('const val doubleConst: Double = 3.14')); + expect(code, contains('const val boolConst: Boolean = true')); + }); } diff --git a/packages/pigeon/test/objc_generator_test.dart b/packages/pigeon/test/objc_generator_test.dart index a5f8179fe84c..8bda38df3cd2 100644 --- a/packages/pigeon/test/objc_generator_test.dart +++ b/packages/pigeon/test/objc_generator_test.dart @@ -3548,4 +3548,51 @@ void main() { expect(code, contains('- (NSString *)description {')); expect(code, contains('return [NSString stringWithFormat:@"ABCFoo()"];')); }); + + test('gen constants', () { + final root = Root( + apis: [], + classes: [], + enums: [], + constants: [ + Constant( + name: 'stringConst', + type: const TypeDeclaration(baseName: 'String', isNullable: false), + value: 'hello', + ), + Constant( + name: 'intConst', + type: const TypeDeclaration(baseName: 'int', isNullable: false), + value: 42, + ), + Constant( + name: 'doubleConst', + type: const TypeDeclaration(baseName: 'double', isNullable: false), + value: 3.14, + ), + Constant( + name: 'boolConst', + type: const TypeDeclaration(baseName: 'bool', isNullable: false), + value: true, + ), + ], + ); + final sink = StringBuffer(); + const generator = ObjcGenerator(); + final generatorOptions = OutputFileOptions( + fileType: FileType.header, + languageOptions: const InternalObjcOptions( + prefix: 'ABC', + objcHeaderOut: '', + objcSourceOut: '', + headerIncludePath: '', + ), + ); + generator.generate(generatorOptions, root, sink, dartPackageName: DEFAULT_PACKAGE_NAME); + final code = sink.toString(); + expect(code, contains('static NSString *const ABCstringConst = @"hello";')); + expect(code, contains('static const NSInteger ABCintConst = 42;')); + expect(code, contains('static const double ABCdoubleConst = 3.14;')); + expect(code, contains('static const BOOL ABCboolConst = YES;')); + }); } diff --git a/packages/pigeon/test/pigeon_lib_test.dart b/packages/pigeon/test/pigeon_lib_test.dart index 7c319672e81d..1ce051bff619 100644 --- a/packages/pigeon/test/pigeon_lib_test.dart +++ b/packages/pigeon/test/pigeon_lib_test.dart @@ -1769,4 +1769,118 @@ abstract class Api { await completer.future; }); }); + + group('constants parsing', () { + test('valid constants', () { + const code = ''' +const String myString = 'hello'; +const int myInt = 42; +const double myDouble = 3.14; +const bool myBool = true; +const int myNegativeInt = -10; +const double myNegativeDouble = -2.5; +const String myAdjacentString = 'hello ' 'world'; +'''; + final ParseResults parseResult = parseSource(code); + expect(parseResult.errors, isEmpty); + expect(parseResult.root.constants.length, 7); + + final Constant myString = parseResult.root.constants[0]; + expect(myString.name, 'myString'); + expect(myString.type.baseName, 'String'); + expect(myString.value, 'hello'); + + final Constant myInt = parseResult.root.constants[1]; + expect(myInt.name, 'myInt'); + expect(myInt.type.baseName, 'int'); + expect(myInt.value, 42); + + final Constant myDouble = parseResult.root.constants[2]; + expect(myDouble.name, 'myDouble'); + expect(myDouble.type.baseName, 'double'); + expect(myDouble.value, 3.14); + + final Constant myBool = parseResult.root.constants[3]; + expect(myBool.name, 'myBool'); + expect(myBool.type.baseName, 'bool'); + expect(myBool.value, true); + + final Constant myNegativeInt = parseResult.root.constants[4]; + expect(myNegativeInt.name, 'myNegativeInt'); + expect(myNegativeInt.type.baseName, 'int'); + expect(myNegativeInt.value, -10); + + final Constant myNegativeDouble = parseResult.root.constants[5]; + expect(myNegativeDouble.name, 'myNegativeDouble'); + expect(myNegativeDouble.type.baseName, 'double'); + expect(myNegativeDouble.value, -2.5); + + final Constant myAdjacentString = parseResult.root.constants[6]; + expect(myAdjacentString.name, 'myAdjacentString'); + expect(myAdjacentString.type.baseName, 'String'); + expect(myAdjacentString.value, 'hello world'); + }); + + test('missing type annotation error', () { + const code = ''' +const myConst = 42; +'''; + final ParseResults parseResult = parseSource(code); + expect(parseResult.errors, isNotEmpty); + expect( + parseResult.errors[0].message, + contains('Top-level constants must have an explicit type annotation.'), + ); + }); + + test('unsupported constant type error', () { + const code = ''' +const List myConst = 42; +'''; + final ParseResults parseResult = parseSource(code); + expect(parseResult.errors, isNotEmpty); + expect( + parseResult.errors[0].message, + contains( + 'Unsupported constant type: "List". Only String, int, double, and bool are supported.', + ), + ); + }); + + test('constant value type mismatch error', () { + const code = ''' +const String myConst = 42; +'''; + final ParseResults parseResult = parseSource(code); + expect(parseResult.errors, isNotEmpty); + expect( + parseResult.errors[0].message, + contains('Constant "myConst" type is String but value is int.'), + ); + }); + + test('unsupported expression type error', () { + const code = ''' +const int myConst = 1 + 2; +'''; + final ParseResults parseResult = parseSource(code); + expect(parseResult.errors, isNotEmpty); + expect( + parseResult.errors[0].message, + contains('Unsupported expression type BinaryExpressionImpl for constant initializer.'), + ); + }); + + test('string interpolation error', () { + const code = r''' +const String myConst = 'hello ${1 + 2}'; +'''; + final ParseResults parseResult = parseSource(code); + expect(parseResult.errors, isNotEmpty); + expect( + parseResult.errors[0].message, + contains('String interpolation is not supported in Pigeon constants.'), + ); + }); + }); } diff --git a/packages/pigeon/test/swift_generator_test.dart b/packages/pigeon/test/swift_generator_test.dart index 5c5f0417fe15..31032779c9d6 100644 --- a/packages/pigeon/test/swift_generator_test.dart +++ b/packages/pigeon/test/swift_generator_test.dart @@ -1537,4 +1537,43 @@ void main() { expect(code, contains('public var description: String {')); expect(code, contains(r'return "Foobar(field1: \(String(describing: field1)))"')); }); + + test('gen constants', () { + final root = Root( + apis: [], + classes: [], + enums: [], + constants: [ + Constant( + name: 'stringConst', + type: const TypeDeclaration(baseName: 'String', isNullable: false), + value: 'hello', + ), + Constant( + name: 'intConst', + type: const TypeDeclaration(baseName: 'int', isNullable: false), + value: 42, + ), + Constant( + name: 'doubleConst', + type: const TypeDeclaration(baseName: 'double', isNullable: false), + value: 3.14, + ), + Constant( + name: 'boolConst', + type: const TypeDeclaration(baseName: 'bool', isNullable: false), + value: true, + ), + ], + ); + final sink = StringBuffer(); + const swiftOptions = InternalSwiftOptions(swiftOut: ''); + const generator = SwiftGenerator(); + generator.generate(swiftOptions, root, sink, dartPackageName: DEFAULT_PACKAGE_NAME); + final code = sink.toString(); + expect(code, contains('let stringConst: String = "hello"')); + expect(code, contains('let intConst: Int64 = 42')); + expect(code, contains('let doubleConst: Double = 3.14')); + expect(code, contains('let boolConst: Bool = true')); + }); } From e91f502b5ee5f63b15fbf610d92372deccb3ffa0 Mon Sep 17 00:00:00 2001 From: tarrinneal Date: Fri, 26 Jun 2026 19:57:28 -0700 Subject: [PATCH 2/3] feedback and edge cases --- .../src/main/java/io/flutter/plugins/Messages.java | 6 ++++++ .../dev/flutter/pigeon_example_app/Messages.g.kt | 5 +++++ .../pigeon/example/app/ios/Runner/Messages.g.swift | 5 +++++ .../pigeon/example/app/lib/src/messages.g.dart | 5 +++++ packages/pigeon/example/app/linux/messages.g.h | 5 +++++ .../pigeon/example/app/macos/Runner/messages.g.h | 5 +++++ .../pigeon/example/app/windows/runner/messages.g.h | 5 +++++ packages/pigeon/lib/src/generator_tools.dart | 3 +-- packages/pigeon/lib/src/java/java_generator.dart | 14 ++++++++++++-- .../pigeon/lib/src/kotlin/kotlin_generator.dart | 2 +- packages/pigeon/lib/src/pigeon_lib_internal.dart | 6 +++++- packages/pigeon/pigeons/core_tests.dart | 1 + .../alternate_language_test_plugin/CoreTests.java | 7 ++++--- .../DataClassMethodsTest.java | 7 ++++--- .../alternate_language_test_plugin/CoreTests.gen.h | 1 + .../example/ios/RunnerTests/DataClassMethodsTest.m | 1 + .../lib/integration_tests.dart | 1 + .../lib/src/generated/core_tests.gen.dart | 1 + .../test/data_class_methods_test.dart | 1 + .../com/example/test_plugin/CoreTests.gen.kt | 1 + .../example/test_plugin/DataClassMethodsTest.kt | 1 + .../Sources/test_plugin/CoreTests.gen.swift | 1 + .../ios/RunnerTests/DataClassMethodsTests.swift | 1 + .../test_plugin/linux/pigeon/core_tests.gen.h | 2 ++ .../linux/test/data_class_methods_test.cc | 1 + .../test_plugin/windows/pigeon/core_tests.gen.h | 2 ++ .../windows/test/data_class_methods_test.cpp | 1 + packages/pigeon/test/java_generator_test.dart | 8 ++++---- packages/pigeon/test/kotlin_generator_test.dart | 12 ++++++++++++ 29 files changed, 95 insertions(+), 16 deletions(-) diff --git a/packages/pigeon/example/app/android/app/src/main/java/io/flutter/plugins/Messages.java b/packages/pigeon/example/app/android/app/src/main/java/io/flutter/plugins/Messages.java index 75a676627eca..ae6eb680e1d2 100644 --- a/packages/pigeon/example/app/android/app/src/main/java/io/flutter/plugins/Messages.java +++ b/packages/pigeon/example/app/android/app/src/main/java/io/flutter/plugins/Messages.java @@ -27,6 +27,12 @@ /** Generated class from Pigeon. */ @SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression", "serial"}) public class Messages { + + public static final String aStringConstant = "stringConstantValue"; + public static final long anIntConstant = 42L; + public static final double aDoubleConstant = 3.14; + public static final boolean aBoolConstant = true; + static boolean pigeonDoubleEquals(double a, double b) { // Normalize -0.0 to 0.0 and handle NaN equality. return (a == 0.0 ? 0.0 : a) == (b == 0.0 ? 0.0 : b) || (Double.isNaN(a) && Double.isNaN(b)); diff --git a/packages/pigeon/example/app/android/app/src/main/kotlin/dev/flutter/pigeon_example_app/Messages.g.kt b/packages/pigeon/example/app/android/app/src/main/kotlin/dev/flutter/pigeon_example_app/Messages.g.kt index e81766320f31..28fa69ce42b6 100644 --- a/packages/pigeon/example/app/android/app/src/main/kotlin/dev/flutter/pigeon_example_app/Messages.g.kt +++ b/packages/pigeon/example/app/android/app/src/main/kotlin/dev/flutter/pigeon_example_app/Messages.g.kt @@ -13,6 +13,11 @@ import io.flutter.plugin.common.StandardMessageCodec import java.io.ByteArrayOutputStream import java.nio.ByteBuffer +const val aStringConstant: String = "stringConstantValue" +const val anIntConstant: Long = 42L +const val aDoubleConstant: Double = 3.14 +const val aBoolConstant: Boolean = true + private object MessagesPigeonUtils { fun createConnectionError(channelName: String): FlutterError { diff --git a/packages/pigeon/example/app/ios/Runner/Messages.g.swift b/packages/pigeon/example/app/ios/Runner/Messages.g.swift index 686f51fcb553..59f44d8d0094 100644 --- a/packages/pigeon/example/app/ios/Runner/Messages.g.swift +++ b/packages/pigeon/example/app/ios/Runner/Messages.g.swift @@ -14,6 +14,11 @@ import Foundation #error("Unsupported platform.") #endif +public let aStringConstant: String = "stringConstantValue" +public let anIntConstant: Int64 = 42 +public let aDoubleConstant: Double = 3.14 +public let aBoolConstant: Bool = true + /// Error class for passing custom error details to Dart side. final class PigeonError: Error { let code: String diff --git a/packages/pigeon/example/app/lib/src/messages.g.dart b/packages/pigeon/example/app/lib/src/messages.g.dart index 10f648a9f714..573e95b06106 100644 --- a/packages/pigeon/example/app/lib/src/messages.g.dart +++ b/packages/pigeon/example/app/lib/src/messages.g.dart @@ -12,6 +12,11 @@ import 'dart:typed_data' show Float64List, Int32List, Int64List; import 'package:flutter/services.dart'; import 'package:meta/meta.dart' show immutable, protected, visibleForTesting; +const String aStringConstant = 'stringConstantValue'; +const int anIntConstant = 42; +const double aDoubleConstant = 3.14; +const bool aBoolConstant = true; + Object? _extractReplyValueOrThrow( List? replyList, String channelName, { diff --git a/packages/pigeon/example/app/linux/messages.g.h b/packages/pigeon/example/app/linux/messages.g.h index 45a903dbb4c2..38c85f4d1eda 100644 --- a/packages/pigeon/example/app/linux/messages.g.h +++ b/packages/pigeon/example/app/linux/messages.g.h @@ -11,6 +11,11 @@ G_BEGIN_DECLS +#define PIGEON_EXAMPLE_PACKAGE_A_STRING_CONSTANT "stringConstantValue" +#define PIGEON_EXAMPLE_PACKAGE_AN_INT_CONSTANT 42 +#define PIGEON_EXAMPLE_PACKAGE_A_DOUBLE_CONSTANT 3.14 +#define PIGEON_EXAMPLE_PACKAGE_A_BOOL_CONSTANT TRUE + /** * PigeonExamplePackageCode: * PIGEON_EXAMPLE_PACKAGE_CODE_ONE: diff --git a/packages/pigeon/example/app/macos/Runner/messages.g.h b/packages/pigeon/example/app/macos/Runner/messages.g.h index 3f08b096986f..2a862dafbbb3 100644 --- a/packages/pigeon/example/app/macos/Runner/messages.g.h +++ b/packages/pigeon/example/app/macos/Runner/messages.g.h @@ -13,6 +13,11 @@ NS_ASSUME_NONNULL_BEGIN +static NSString *const PGNaStringConstant = @"stringConstantValue"; +static const NSInteger PGNanIntConstant = 42; +static const double PGNaDoubleConstant = 3.14; +static const BOOL PGNaBoolConstant = YES; + typedef NS_ENUM(NSUInteger, PGNCode) { PGNCodeOne = 0, PGNCodeTwo = 1, diff --git a/packages/pigeon/example/app/windows/runner/messages.g.h b/packages/pigeon/example/app/windows/runner/messages.g.h index 2ad87b1205df..22756881cc41 100644 --- a/packages/pigeon/example/app/windows/runner/messages.g.h +++ b/packages/pigeon/example/app/windows/runner/messages.g.h @@ -20,6 +20,11 @@ namespace pigeon_example { // Generated class from Pigeon. +inline constexpr const char* aStringConstant = "stringConstantValue"; +inline constexpr int64_t anIntConstant = 42; +inline constexpr double aDoubleConstant = 3.14; +inline constexpr bool aBoolConstant = true; + class FlutterError { public: explicit FlutterError(const std::string& code) : code_(code) {} diff --git a/packages/pigeon/lib/src/generator_tools.dart b/packages/pigeon/lib/src/generator_tools.dart index c685bf872245..47433dd5159c 100644 --- a/packages/pigeon/lib/src/generator_tools.dart +++ b/packages/pigeon/lib/src/generator_tools.dart @@ -878,8 +878,7 @@ String escapeStringDoubleQuotes(String value) { .replaceAll('\\', r'\\') // ignore: use_raw_strings .replaceAll('"', r'\"') .replaceAll('\n', r'\n') - .replaceAll('\r', r'\r') - .replaceAll(r'$', r'\$'); + .replaceAll('\r', r'\r'); } /// Escapes special characters in a string for use in single-quoted string literals. diff --git a/packages/pigeon/lib/src/java/java_generator.dart b/packages/pigeon/lib/src/java/java_generator.dart index 443e6a9ba0a6..ca5fc6ce501a 100644 --- a/packages/pigeon/lib/src/java/java_generator.dart +++ b/packages/pigeon/lib/src/java/java_generator.dart @@ -215,7 +215,8 @@ class JavaGenerator extends StructuredGenerator { indent.newln(); for (final Constant constant in root.constants) { addDocumentationComments(indent, constant.documentationComments, _docCommentSpec); - final String javaType = _javaTypeForBuiltinDartType(constant.type) ?? 'Object'; + final String javaType = + _javaTypeForBuiltinDartType(constant.type, primitive: true) ?? 'Object'; final String formattedValue = _formatJavaValue(constant.type.baseName, constant.value); indent.writeln('public static final $javaType ${constant.name} = $formattedValue;'); } @@ -1418,7 +1419,16 @@ String _javaTypeForBuiltinGenericDartType(TypeDeclaration type, int numberTypeAr } } -String? _javaTypeForBuiltinDartType(TypeDeclaration type) { +String? _javaTypeForBuiltinDartType(TypeDeclaration type, {bool primitive = false}) { + if (primitive) { + if (type.baseName == 'bool') { + return 'boolean'; + } else if (type.baseName == 'int') { + return 'long'; + } else if (type.baseName == 'double') { + return 'double'; + } + } const javaTypeForDartTypeMap = { 'bool': 'Boolean', 'int': 'Long', diff --git a/packages/pigeon/lib/src/kotlin/kotlin_generator.dart b/packages/pigeon/lib/src/kotlin/kotlin_generator.dart index bfbda2201074..20ecbe1cac22 100644 --- a/packages/pigeon/lib/src/kotlin/kotlin_generator.dart +++ b/packages/pigeon/lib/src/kotlin/kotlin_generator.dart @@ -260,7 +260,7 @@ class KotlinGenerator extends StructuredGenerator { String _formatKotlinValue(String type, Object value) { if (type == 'String') { - final String escaped = escapeStringDoubleQuotes(value.toString()); + final String escaped = escapeStringDoubleQuotes(value.toString()).replaceAll(r'$', r'\$'); return '"$escaped"'; } else if (type == 'int') { return '${value}L'; diff --git a/packages/pigeon/lib/src/pigeon_lib_internal.dart b/packages/pigeon/lib/src/pigeon_lib_internal.dart index 9267f070d469..77f7df41a93a 100644 --- a/packages/pigeon/lib/src/pigeon_lib_internal.dart +++ b/packages/pigeon/lib/src/pigeon_lib_internal.dart @@ -1450,11 +1450,15 @@ class RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { continue; } - final Object? value = _evaluateExpression(initializer); + Object? value = _evaluateExpression(initializer); if (value == null) { continue; } + if (_getNamedTypeQualifiedName(typeAnnotation) == 'double' && value is num) { + value = value.toDouble(); + } + final type = TypeDeclaration( baseName: _getNamedTypeQualifiedName(typeAnnotation), isNullable: typeAnnotation.question != null, diff --git a/packages/pigeon/pigeons/core_tests.dart b/packages/pigeon/pigeons/core_tests.dart index de1458153277..3022d37c6c84 100644 --- a/packages/pigeon/pigeons/core_tests.dart +++ b/packages/pigeon/pigeons/core_tests.dart @@ -5,6 +5,7 @@ import 'package:pigeon/pigeon.dart'; const String aStringConstant = 'stringConstantValue'; +const String aStringConstantWithEscapes = r'string\\$ConstantValue'; const int anIntConstant = 42; const double aDoubleConstant = 3.14; const bool aBoolConstant = true; diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/main/java/com/example/alternate_language_test_plugin/CoreTests.java b/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/main/java/com/example/alternate_language_test_plugin/CoreTests.java index 8a55eb38651e..859567d780e8 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/main/java/com/example/alternate_language_test_plugin/CoreTests.java +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/main/java/com/example/alternate_language_test_plugin/CoreTests.java @@ -32,9 +32,10 @@ public class CoreTests { public static final String aStringConstant = "stringConstantValue"; - public static final Long anIntConstant = 42L; - public static final Double aDoubleConstant = 3.14; - public static final Boolean aBoolConstant = true; + public static final String aStringConstantWithEscapes = "string\\\\$ConstantValue"; + public static final long anIntConstant = 42L; + public static final double aDoubleConstant = 3.14; + public static final boolean aBoolConstant = true; static boolean pigeonDoubleEquals(double a, double b) { // Normalize -0.0 to 0.0 and handle NaN equality. diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/test/java/com/example/alternate_language_test_plugin/DataClassMethodsTest.java b/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/test/java/com/example/alternate_language_test_plugin/DataClassMethodsTest.java index bce312f3e1c6..cc236ececd77 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/test/java/com/example/alternate_language_test_plugin/DataClassMethodsTest.java +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/test/java/com/example/alternate_language_test_plugin/DataClassMethodsTest.java @@ -409,8 +409,9 @@ public void nestedZeroArrayEquality() { @Test public void testConstants() { assertEquals("stringConstantValue", CoreTests.aStringConstant); - assertEquals(Long.valueOf(42L), CoreTests.anIntConstant); - assertEquals(Double.valueOf(3.14), CoreTests.aDoubleConstant); - assertEquals(Boolean.TRUE, CoreTests.aBoolConstant); + assertEquals("string\\\\$ConstantValue", CoreTests.aStringConstantWithEscapes); + assertEquals(42L, CoreTests.anIntConstant); + assertEquals(3.14, CoreTests.aDoubleConstant, 0.0001); + assertTrue(CoreTests.aBoolConstant); } } diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/darwin/alternate_language_test_plugin/Sources/alternate_language_test_plugin/include/alternate_language_test_plugin/CoreTests.gen.h b/packages/pigeon/platform_tests/alternate_language_test_plugin/darwin/alternate_language_test_plugin/Sources/alternate_language_test_plugin/include/alternate_language_test_plugin/CoreTests.gen.h index 38108546c930..86c7b3221c58 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/darwin/alternate_language_test_plugin/Sources/alternate_language_test_plugin/include/alternate_language_test_plugin/CoreTests.gen.h +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/darwin/alternate_language_test_plugin/Sources/alternate_language_test_plugin/include/alternate_language_test_plugin/CoreTests.gen.h @@ -15,6 +15,7 @@ NS_ASSUME_NONNULL_BEGIN static NSString *const FLTaStringConstant = @"stringConstantValue"; +static NSString *const FLTaStringConstantWithEscapes = @"string\\\\$ConstantValue"; static const NSInteger FLTanIntConstant = 42; static const double FLTaDoubleConstant = 3.14; static const BOOL FLTaBoolConstant = YES; diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/DataClassMethodsTest.m b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/DataClassMethodsTest.m index 82817970d4fb..14ebc6998f18 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/DataClassMethodsTest.m +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/DataClassMethodsTest.m @@ -255,6 +255,7 @@ - (void)testNSNullPropertyEquality { - (void)testConstants { XCTAssertEqualObjects(FLTaStringConstant, @"stringConstantValue"); + XCTAssertEqualObjects(FLTaStringConstantWithEscapes, @"string\\\\$ConstantValue"); XCTAssertEqual(FLTanIntConstant, 42); XCTAssertEqual(FLTaDoubleConstant, 3.14); XCTAssertEqual(FLTaBoolConstant, YES); diff --git a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/integration_tests.dart b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/integration_tests.dart index fb6fa69dc7c2..9515b5d238e3 100644 --- a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/integration_tests.dart +++ b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/integration_tests.dart @@ -2798,6 +2798,7 @@ void runPigeonIntegrationTests(TargetGenerator targetGenerator) { testWidgets('constants are generated correctly', (WidgetTester _) async { expect(aStringConstant, 'stringConstantValue'); + expect(aStringConstantWithEscapes, r'string\\$ConstantValue'); expect(anIntConstant, 42); expect(aDoubleConstant, 3.14); expect(aBoolConstant, true); diff --git a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/core_tests.gen.dart b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/core_tests.gen.dart index 7e55f9809435..bf6f50943bd6 100644 --- a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/core_tests.gen.dart +++ b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/core_tests.gen.dart @@ -14,6 +14,7 @@ import 'package:flutter/services.dart'; import 'package:meta/meta.dart' show immutable, protected, visibleForTesting; const String aStringConstant = 'stringConstantValue'; +const String aStringConstantWithEscapes = 'string\\\\\$ConstantValue'; const int anIntConstant = 42; const double aDoubleConstant = 3.14; const bool aBoolConstant = true; diff --git a/packages/pigeon/platform_tests/shared_test_plugin_code/test/data_class_methods_test.dart b/packages/pigeon/platform_tests/shared_test_plugin_code/test/data_class_methods_test.dart index a64c03f80536..2880c403e8e0 100644 --- a/packages/pigeon/platform_tests/shared_test_plugin_code/test/data_class_methods_test.dart +++ b/packages/pigeon/platform_tests/shared_test_plugin_code/test/data_class_methods_test.dart @@ -202,6 +202,7 @@ void main() { test('constants', () { expect(aStringConstant, 'stringConstantValue'); + expect(aStringConstantWithEscapes, r'string\\$ConstantValue'); expect(anIntConstant, 42); expect(aDoubleConstant, 3.14); expect(aBoolConstant, true); diff --git a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/CoreTests.gen.kt b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/CoreTests.gen.kt index 796a724b670f..77a84a4e53bd 100644 --- a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/CoreTests.gen.kt +++ b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/CoreTests.gen.kt @@ -17,6 +17,7 @@ import java.io.ByteArrayOutputStream import java.nio.ByteBuffer const val aStringConstant: String = "stringConstantValue" +const val aStringConstantWithEscapes: String = "string\\\\\$ConstantValue" const val anIntConstant: Long = 42L const val aDoubleConstant: Double = 3.14 const val aBoolConstant: Boolean = true diff --git a/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/DataClassMethodsTest.kt b/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/DataClassMethodsTest.kt index 81ce0b3adf1d..d04d4ea58109 100644 --- a/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/DataClassMethodsTest.kt +++ b/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/DataClassMethodsTest.kt @@ -307,6 +307,7 @@ internal class DataClassMethodsTest { @Test fun testConstants() { assertEquals("stringConstantValue", aStringConstant) + assertEquals("string\\\\\$ConstantValue", aStringConstantWithEscapes) assertEquals(42L, anIntConstant) assertEquals(3.14, aDoubleConstant, 0.0) assertEquals(true, aBoolConstant) diff --git a/packages/pigeon/platform_tests/test_plugin/darwin/test_plugin/Sources/test_plugin/CoreTests.gen.swift b/packages/pigeon/platform_tests/test_plugin/darwin/test_plugin/Sources/test_plugin/CoreTests.gen.swift index c290eae38f0b..e9243fbaaed6 100644 --- a/packages/pigeon/platform_tests/test_plugin/darwin/test_plugin/Sources/test_plugin/CoreTests.gen.swift +++ b/packages/pigeon/platform_tests/test_plugin/darwin/test_plugin/Sources/test_plugin/CoreTests.gen.swift @@ -16,6 +16,7 @@ import Foundation #endif public let aStringConstant: String = "stringConstantValue" +public let aStringConstantWithEscapes: String = "string\\\\$ConstantValue" public let anIntConstant: Int64 = 42 public let aDoubleConstant: Double = 3.14 public let aBoolConstant: Bool = true diff --git a/packages/pigeon/platform_tests/test_plugin/example/ios/RunnerTests/DataClassMethodsTests.swift b/packages/pigeon/platform_tests/test_plugin/example/ios/RunnerTests/DataClassMethodsTests.swift index 0b9a738663ad..6a9faf3ba33e 100644 --- a/packages/pigeon/platform_tests/test_plugin/example/ios/RunnerTests/DataClassMethodsTests.swift +++ b/packages/pigeon/platform_tests/test_plugin/example/ios/RunnerTests/DataClassMethodsTests.swift @@ -387,6 +387,7 @@ struct DataClassMethodsTests { @Test func testConstants() { #expect(aStringConstant == "stringConstantValue") + #expect(aStringConstantWithEscapes == "string\\\\$ConstantValue") #expect(anIntConstant == 42) #expect(aDoubleConstant == 3.14) #expect(aBoolConstant == true) diff --git a/packages/pigeon/platform_tests/test_plugin/linux/pigeon/core_tests.gen.h b/packages/pigeon/platform_tests/test_plugin/linux/pigeon/core_tests.gen.h index 37d2c850e062..303d4aa1a1cf 100644 --- a/packages/pigeon/platform_tests/test_plugin/linux/pigeon/core_tests.gen.h +++ b/packages/pigeon/platform_tests/test_plugin/linux/pigeon/core_tests.gen.h @@ -13,6 +13,8 @@ G_BEGIN_DECLS #define CORE_TESTS_PIGEON_TEST_A_STRING_CONSTANT "stringConstantValue" +#define CORE_TESTS_PIGEON_TEST_A_STRING_CONSTANT_WITH_ESCAPES \ + "string\\\\$ConstantValue" #define CORE_TESTS_PIGEON_TEST_AN_INT_CONSTANT 42 #define CORE_TESTS_PIGEON_TEST_A_DOUBLE_CONSTANT 3.14 #define CORE_TESTS_PIGEON_TEST_A_BOOL_CONSTANT TRUE diff --git a/packages/pigeon/platform_tests/test_plugin/linux/test/data_class_methods_test.cc b/packages/pigeon/platform_tests/test_plugin/linux/test/data_class_methods_test.cc index 25f839e4a9bf..8d1c5961113c 100644 --- a/packages/pigeon/platform_tests/test_plugin/linux/test/data_class_methods_test.cc +++ b/packages/pigeon/platform_tests/test_plugin/linux/test/data_class_methods_test.cc @@ -306,6 +306,7 @@ TEST(Equality, ToStringSnapshot) { TEST(Constants, VerifyConstants) { EXPECT_STREQ(CORE_TESTS_PIGEON_TEST_A_STRING_CONSTANT, "stringConstantValue"); + EXPECT_STREQ(CORE_TESTS_PIGEON_TEST_A_STRING_CONSTANT_WITH_ESCAPES, "string\\\\$ConstantValue"); EXPECT_EQ(CORE_TESTS_PIGEON_TEST_AN_INT_CONSTANT, 42); EXPECT_DOUBLE_EQ(CORE_TESTS_PIGEON_TEST_A_DOUBLE_CONSTANT, 3.14); EXPECT_TRUE(CORE_TESTS_PIGEON_TEST_A_BOOL_CONSTANT); diff --git a/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.h b/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.h index 722328ea0649..605b696d400a 100644 --- a/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.h +++ b/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.h @@ -24,6 +24,8 @@ class CoreTestsTest; // Generated class from Pigeon. inline constexpr const char* aStringConstant = "stringConstantValue"; +inline constexpr const char* aStringConstantWithEscapes = + "string\\\\$ConstantValue"; inline constexpr int64_t anIntConstant = 42; inline constexpr double aDoubleConstant = 3.14; inline constexpr bool aBoolConstant = true; diff --git a/packages/pigeon/platform_tests/test_plugin/windows/test/data_class_methods_test.cpp b/packages/pigeon/platform_tests/test_plugin/windows/test/data_class_methods_test.cpp index 53b0b91720cc..78545691c5e8 100644 --- a/packages/pigeon/platform_tests/test_plugin/windows/test/data_class_methods_test.cpp +++ b/packages/pigeon/platform_tests/test_plugin/windows/test/data_class_methods_test.cpp @@ -155,6 +155,7 @@ TEST(SerializationTests, StreamOutputFullSnapshot) { TEST(ConstantTests, VerifyConstants) { EXPECT_EQ(std::string(aStringConstant), "stringConstantValue"); + EXPECT_EQ(std::string(aStringConstantWithEscapes), "string\\\\$ConstantValue"); EXPECT_EQ(anIntConstant, 42); EXPECT_DOUBLE_EQ(aDoubleConstant, 3.14); EXPECT_EQ(aBoolConstant, true); diff --git a/packages/pigeon/test/java_generator_test.dart b/packages/pigeon/test/java_generator_test.dart index be307a6bb126..c6907c77b570 100644 --- a/packages/pigeon/test/java_generator_test.dart +++ b/packages/pigeon/test/java_generator_test.dart @@ -1615,10 +1615,10 @@ void main() { const javaOptions = InternalJavaOptions(className: 'Messages', javaOut: ''); const generator = JavaGenerator(); generator.generate(javaOptions, root, sink, dartPackageName: DEFAULT_PACKAGE_NAME); - final code = sink.toString(); + final String code = sink.toString(); expect(code, contains('public static final String stringConst = "hello";')); - expect(code, contains('public static final Long intConst = 42L;')); - expect(code, contains('public static final Double doubleConst = 3.14;')); - expect(code, contains('public static final Boolean boolConst = true;')); + expect(code, contains('public static final long intConst = 42L;')); + expect(code, contains('public static final double doubleConst = 3.14;')); + expect(code, contains('public static final boolean boolConst = true;')); }); } diff --git a/packages/pigeon/test/kotlin_generator_test.dart b/packages/pigeon/test/kotlin_generator_test.dart index 0610c233414b..4daaadba5b79 100644 --- a/packages/pigeon/test/kotlin_generator_test.dart +++ b/packages/pigeon/test/kotlin_generator_test.dart @@ -1854,6 +1854,16 @@ void main() { type: const TypeDeclaration(baseName: 'bool', isNullable: false), value: true, ), + Constant( + name: 'stringWithBackslashDollar', + type: const TypeDeclaration(baseName: 'String', isNullable: false), + value: r'\$', + ), + Constant( + name: 'stringWithTwoBackslashesDollar', + type: const TypeDeclaration(baseName: 'String', isNullable: false), + value: r'\\$', + ), ], ); final sink = StringBuffer(); @@ -1865,5 +1875,7 @@ void main() { expect(code, contains('const val intConst: Long = 42L')); expect(code, contains('const val doubleConst: Double = 3.14')); expect(code, contains('const val boolConst: Boolean = true')); + expect(code, contains(r'const val stringWithBackslashDollar: String = "\\\$"')); + expect(code, contains(r'const val stringWithTwoBackslashesDollar: String = "\\\\\$"')); }); } From 27d5df578125b798e508ddde53c30da2ad327f9c Mon Sep 17 00:00:00 2001 From: tarrinneal Date: Mon, 29 Jun 2026 16:01:36 -0700 Subject: [PATCH 3/3] lints --- .../pigeon/lib/src/dart/dart_generator.dart | 43 ++++++++++++++++++- .../lib/src/generated/core_tests.gen.dart | 2 +- .../linux/test/data_class_methods_test.cc | 3 +- .../windows/test/data_class_methods_test.cpp | 3 +- packages/pigeon/test/java_generator_test.dart | 2 +- 5 files changed, 47 insertions(+), 6 deletions(-) diff --git a/packages/pigeon/lib/src/dart/dart_generator.dart b/packages/pigeon/lib/src/dart/dart_generator.dart index 2866ddd5a4e2..1fe9063bea61 100644 --- a/packages/pigeon/lib/src/dart/dart_generator.dart +++ b/packages/pigeon/lib/src/dart/dart_generator.dart @@ -206,13 +206,52 @@ class DartGenerator extends StructuredGenerator { String _formatValue(String type, Object value) { if (type == 'String') { - final String escaped = escapeStringSingleQuotes(value.toString()); - return "'$escaped'"; + return _makeDartStringLiteral(value.toString()); } else { return value.toString(); } } + String _makeDartStringLiteral(String valStr) { + final bool hasSpecial = + // ignore: use_raw_strings + valStr.contains('\\') || + valStr.contains(r'$') || + // ignore: use_raw_strings + valStr.contains('\n') || + // ignore: use_raw_strings + valStr.contains('\r'); + + if (!hasSpecial) { + if (!valStr.contains("'")) { + return "'$valStr'"; + } + if (!valStr.contains('"')) { + return '"$valStr"'; + } + return "r'''$valStr'''"; + } + + if (!valStr.contains('\n') && !valStr.contains('\r')) { + if (!valStr.contains("'")) { + return "r'$valStr'"; + } + if (!valStr.contains('"')) { + return 'r"$valStr"'; + } + } + + if (!valStr.contains("'''")) { + return "r'''$valStr'''"; + } + if (!valStr.contains('"""')) { + return 'r"""$valStr"""'; + } + + final String escaped = escapeStringSingleQuotes(valStr); + return "'$escaped'"; + } + @override void writeEnum( InternalDartOptions generatorOptions, diff --git a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/core_tests.gen.dart b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/core_tests.gen.dart index bf6f50943bd6..08b77da18f18 100644 --- a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/core_tests.gen.dart +++ b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/core_tests.gen.dart @@ -14,7 +14,7 @@ import 'package:flutter/services.dart'; import 'package:meta/meta.dart' show immutable, protected, visibleForTesting; const String aStringConstant = 'stringConstantValue'; -const String aStringConstantWithEscapes = 'string\\\\\$ConstantValue'; +const String aStringConstantWithEscapes = r'string\\$ConstantValue'; const int anIntConstant = 42; const double aDoubleConstant = 3.14; const bool aBoolConstant = true; diff --git a/packages/pigeon/platform_tests/test_plugin/linux/test/data_class_methods_test.cc b/packages/pigeon/platform_tests/test_plugin/linux/test/data_class_methods_test.cc index 8d1c5961113c..0e5e8504b55b 100644 --- a/packages/pigeon/platform_tests/test_plugin/linux/test/data_class_methods_test.cc +++ b/packages/pigeon/platform_tests/test_plugin/linux/test/data_class_methods_test.cc @@ -306,7 +306,8 @@ TEST(Equality, ToStringSnapshot) { TEST(Constants, VerifyConstants) { EXPECT_STREQ(CORE_TESTS_PIGEON_TEST_A_STRING_CONSTANT, "stringConstantValue"); - EXPECT_STREQ(CORE_TESTS_PIGEON_TEST_A_STRING_CONSTANT_WITH_ESCAPES, "string\\\\$ConstantValue"); + EXPECT_STREQ(CORE_TESTS_PIGEON_TEST_A_STRING_CONSTANT_WITH_ESCAPES, + "string\\\\$ConstantValue"); EXPECT_EQ(CORE_TESTS_PIGEON_TEST_AN_INT_CONSTANT, 42); EXPECT_DOUBLE_EQ(CORE_TESTS_PIGEON_TEST_A_DOUBLE_CONSTANT, 3.14); EXPECT_TRUE(CORE_TESTS_PIGEON_TEST_A_BOOL_CONSTANT); diff --git a/packages/pigeon/platform_tests/test_plugin/windows/test/data_class_methods_test.cpp b/packages/pigeon/platform_tests/test_plugin/windows/test/data_class_methods_test.cpp index 78545691c5e8..ffce1685f426 100644 --- a/packages/pigeon/platform_tests/test_plugin/windows/test/data_class_methods_test.cpp +++ b/packages/pigeon/platform_tests/test_plugin/windows/test/data_class_methods_test.cpp @@ -155,7 +155,8 @@ TEST(SerializationTests, StreamOutputFullSnapshot) { TEST(ConstantTests, VerifyConstants) { EXPECT_EQ(std::string(aStringConstant), "stringConstantValue"); - EXPECT_EQ(std::string(aStringConstantWithEscapes), "string\\\\$ConstantValue"); + EXPECT_EQ(std::string(aStringConstantWithEscapes), + "string\\\\$ConstantValue"); EXPECT_EQ(anIntConstant, 42); EXPECT_DOUBLE_EQ(aDoubleConstant, 3.14); EXPECT_EQ(aBoolConstant, true); diff --git a/packages/pigeon/test/java_generator_test.dart b/packages/pigeon/test/java_generator_test.dart index c6907c77b570..6d2a58420463 100644 --- a/packages/pigeon/test/java_generator_test.dart +++ b/packages/pigeon/test/java_generator_test.dart @@ -1615,7 +1615,7 @@ void main() { const javaOptions = InternalJavaOptions(className: 'Messages', javaOut: ''); const generator = JavaGenerator(); generator.generate(javaOptions, root, sink, dartPackageName: DEFAULT_PACKAGE_NAME); - final String code = sink.toString(); + final code = sink.toString(); expect(code, contains('public static final String stringConst = "hello";')); expect(code, contains('public static final long intConst = 42L;')); expect(code, contains('public static final double doubleConst = 3.14;'));