Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/pigeon/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 27.2.0

* Adds support for sharing constants across platforms.

## 27.1.0

* [swift] Adds `CaseIterable` conformance to generated enums.
Expand Down
15 changes: 15 additions & 0 deletions packages/pigeon/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
<?code-excerpt path-base="example/app"?>
# Pigeon

Pigeon is a code generator tool to make communication between Flutter and the
Expand Down Expand Up @@ -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:

<?code-excerpt "pigeons/messages.dart (constants)"?>
```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`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
5 changes: 5 additions & 0 deletions packages/pigeon/example/app/ios/Runner/Messages.g.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions packages/pigeon/example/app/lib/src/messages.g.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<Object?>? replyList,
String channelName, {
Expand Down
5 changes: 5 additions & 0 deletions packages/pigeon/example/app/linux/messages.g.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
5 changes: 5 additions & 0 deletions packages/pigeon/example/app/macos/Runner/messages.g.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@

NS_ASSUME_NONNULL_BEGIN

static NSString *const PGNaStringConstant = @"stringConstantValue";
Comment thread
tarrinneal marked this conversation as resolved.
Outdated
Comment thread
tarrinneal marked this conversation as resolved.
Outdated
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,
Expand Down
7 changes: 7 additions & 0 deletions packages/pigeon/example/app/pigeons/messages.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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
5 changes: 5 additions & 0 deletions packages/pigeon/example/app/windows/runner/messages.g.h
Original file line number Diff line number Diff line change
Expand Up @@ -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) {}
Expand Down
40 changes: 38 additions & 2 deletions packages/pigeon/lib/src/ast.dart
Original file line number Diff line number Diff line change
Expand Up @@ -785,13 +785,46 @@ 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 <String>[],
});

/// 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<String> 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].
Root({
required this.classes,
required this.apis,
required this.enums,
this.constants = const <Constant>[],
this.containsHostApi = false,
this.containsFlutterApi = false,
this.containsProxyApi = false,
Expand All @@ -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: <Api>[], classes: <Class>[], enums: <Enum>[]);
return Root(apis: <Api>[], classes: <Class>[], enums: <Enum>[], constants: <Constant>[]);
}

/// All the classes contained in the AST.
Expand All @@ -812,6 +845,9 @@ class Root extends Node {
/// All of the enums contained in the AST.
List<Enum> enums;

/// All of the constants contained in the AST.
List<Constant> constants;

/// Whether the root has any Host API definitions.
bool containsHostApi;

Expand All @@ -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)';
}
}
24 changes: 24 additions & 0 deletions packages/pigeon/lib/src/cpp/cpp_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
66 changes: 66 additions & 0 deletions packages/pigeon/lib/src/dart/dart_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,72 @@ class DartGenerator extends StructuredGenerator<InternalDartOptions> {
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') {
return _makeDartStringLiteral(value.toString());
} else {
return value.toString();
}
Comment thread
tarrinneal marked this conversation as resolved.
Outdated
}

String _makeDartStringLiteral(String valStr) {
final bool hasSpecial =
// ignore: use_raw_strings
Comment thread
tarrinneal marked this conversation as resolved.
Outdated
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'''";
Comment thread
tarrinneal marked this conversation as resolved.
Outdated
}

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,
Expand Down
12 changes: 12 additions & 0 deletions packages/pigeon/lib/src/generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ abstract class StructuredGenerator<T extends InternalOptions> 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)) {
Expand Down Expand Up @@ -106,6 +108,16 @@ abstract class StructuredGenerator<T extends InternalOptions> 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.
Expand Down
21 changes: 20 additions & 1 deletion packages/pigeon/lib/src/generator_tools.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -871,3 +871,22 @@ 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
Comment thread
tarrinneal marked this conversation as resolved.
Outdated
.replaceAll('"', r'\"')
.replaceAll('\n', r'\n')
.replaceAll('\r', r'\r');
}
Comment thread
tarrinneal marked this conversation as resolved.

/// 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'\$');
}
Loading
Loading