Skip to content
Open
Show file tree
Hide file tree
Changes from all 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";

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because of the prefixing, the variable name needs to be converted to UpperCamelCase. E.g., PGNAStringConstant.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consts should not be inlined into headers in C. These should be extern declarations, and the definitions should be in the .g.m file.

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();
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Consider a single return statement with a ternary.

}

String _makeDartStringLiteral(String valStr) {
final bool hasSpecial =
// ignore: use_raw_strings

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just use a raw string for this one?

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'''";

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if the string contains '''s? I think you'll need to make the fallback escaping ' instead of using '''.

}

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and below, why not just use raw strings?

.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