blob: 79c51d61e47e1eff4051b16cd8ae8fde27881f60 [file] [edit]
// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:kernel/ast.dart';
import 'package:kernel/core_types.dart' show CoreTypes;
import 'package:kernel/target/targets.dart' show Target;
// Pragmas recognized by the VM
const vmEntryPointPragmaName = "vm:entry-point";
const vmExactResultTypePragmaName = "vm:exact-result-type";
const kResultTypeUsesPassedTypeArguments =
"result-type-uses-passed-type-arguments";
const vmRecognizedPragmaName = "vm:recognized";
const vmDisableUnboxedParametersPragmaName = "vm:disable-unboxed-parameters";
const vmKeepNamePragmaName = "vm:keep-name";
const vmPlatformConstPragmaName = "vm:platform-const";
const vmPlatformConstIfPragmaName = "vm:platform-const-if";
const vmFfiNative = "vm:ffi:native";
const vmSharedPragmaName = "vm:shared";
const vmDeeplyImmutablePragmaName = "vm:deeply-immutable";
const vmInvisiblePragmaName = "vm:invisible";
// Pragmas recognized by dart2wasm
const wasmEntryPointPragmaName = "wasm:entry-point";
const wasmExportPragmaName = "wasm:export";
// Dynamic modules pragmas, recognized both by the VM and dart2wasm
const dynModuleExtendablePragmaName = "dyn-module:extendable";
const dynModuleImplicitlyExtendablePragmaName =
"dyn-module:implicitly-extendable";
const dynModuleCanBeOverriddenPragmaName = "dyn-module:can-be-overridden";
const dynModuleCanBeOverriddenImplicitlyPragmaName =
"dyn-module:can-be-overridden-implicitly";
const dynModuleCallablePragmaName = "dyn-module:callable";
const dynModuleImplicitlyCallablePragmaName = "dyn-module:implicitly-callable";
const dynModuleCanBeUsedAsTypePragmaName = "dyn-module:can-be-used-as-type";
const dynModuleEntryPointPragmaName = "dyn-module:entry-point";
const dynModuleDynamicallyCallablePragmaName =
"dyn-module:dynamically-callable";
const dynModuleImplicitlyDynamicallyCallablePragmaName =
"dyn-module:implicitly-dynamically-callable";
abstract class ParsedPragma {}
enum PragmaEntryPointType {
Default,
Extendable,
ImplicitlyExtendable,
CanBeOverridden,
GetterOnly,
SetterOnly,
CallOnly,
CanBeUsedAsType,
DynamicallyCallable,
}
enum PragmaRecognizedType { AsmIntrinsic, GraphIntrinsic, Other }
class ParsedEntryPointPragma implements ParsedPragma {
final PragmaEntryPointType type;
const ParsedEntryPointPragma(this.type);
}
class ParsedResultTypeByTypePragma implements ParsedPragma {
final DartType type;
final bool resultTypeUsesPassedTypeArguments;
const ParsedResultTypeByTypePragma(
this.type,
this.resultTypeUsesPassedTypeArguments,
);
}
class ParsedResultTypeByPathPragma implements ParsedPragma {
final String path;
const ParsedResultTypeByPathPragma(this.path);
}
class ParsedRecognized implements ParsedPragma {
final PragmaRecognizedType type;
const ParsedRecognized(this.type);
}
class ParsedDisableUnboxedParameters implements ParsedPragma {
const ParsedDisableUnboxedParameters();
}
class ParsedKeepNamePragma implements ParsedPragma {
const ParsedKeepNamePragma();
}
class ParsedPlatformConstPragma implements ParsedPragma {
const ParsedPlatformConstPragma();
}
class ParsedFfiNativePragma implements ParsedPragma {
const ParsedFfiNativePragma();
}
class ParsedDynModuleEntryPointPragma implements ParsedPragma {
const ParsedDynModuleEntryPointPragma();
}
class ParsedVmSharedPragma implements ParsedPragma {
const ParsedVmSharedPragma();
}
class ParsedVmDeeplyImmutablePragma implements ParsedPragma {
const ParsedVmDeeplyImmutablePragma();
}
class ParsedVmInvisiblePragma implements ParsedPragma {
const ParsedVmInvisiblePragma();
}
abstract class PragmaAnnotationParser {
/// May return 'null' if the annotation does not represent a recognized
/// @pragma.
ParsedPragma? parsePragma(Expression annotation);
Iterable<R> parsedPragmas<R extends ParsedPragma>(Iterable<Expression> node);
}
class ConstantPragmaAnnotationParser implements PragmaAnnotationParser {
final CoreTypes coreTypes;
final Target target;
ConstantPragmaAnnotationParser(this.coreTypes, this.target);
ParsedEntryPointPragma? getEntryPointTypeFromOptions(
Constant options,
String pragmaName,
) {
PragmaEntryPointType? type;
if (options is NullConstant) {
type = PragmaEntryPointType.Default;
} else if (options is BoolConstant && options.value == true) {
type = PragmaEntryPointType.Default;
} else if (options is StringConstant) {
if (options.value == "get") {
type = PragmaEntryPointType.GetterOnly;
} else if (options.value == "set") {
type = PragmaEntryPointType.SetterOnly;
} else if (options.value == "call") {
type = PragmaEntryPointType.CallOnly;
} else {
throw "Error: string directive to "
"@pragma('$pragmaName', ...) "
"must be either 'get' or 'set' for fields "
"or 'get' or 'call' for procedures.";
}
}
return type != null ? ParsedEntryPointPragma(type) : null;
}
ParsedPragma? parsePragma(Expression annotation) {
InstanceConstant? pragmaConstant;
if (annotation is ConstantExpression) {
Constant constant = annotation.constant;
if (constant is InstanceConstant) {
if (constant.classNode == coreTypes.pragmaClass) {
pragmaConstant = constant;
}
} else if (constant is UnevaluatedConstant) {
throw 'Error: unevaluated constant $constant';
}
} else if (annotation is InvalidExpression) {
return null;
} else {
throw 'Error: non-constant annotation $annotation';
}
if (pragmaConstant == null) return null;
String pragmaName;
Constant? name =
pragmaConstant.fieldValues[coreTypes.pragmaName.fieldReference];
if (name is StringConstant) {
pragmaName = name.value;
} else {
return null;
}
if (!target.isSupportedPragma(pragmaName)) return null;
Constant options =
pragmaConstant.fieldValues[coreTypes.pragmaOptions.fieldReference]!;
switch (pragmaName) {
case vmEntryPointPragmaName:
return getEntryPointTypeFromOptions(options, pragmaName);
case vmExactResultTypePragmaName:
if (options is TypeLiteralConstant) {
return ParsedResultTypeByTypePragma(options.type, false);
} else if (options is StringConstant) {
return ParsedResultTypeByPathPragma(options.value);
} else if (options is ListConstant &&
options.entries.length == 2 &&
options.entries[0] is TypeLiteralConstant &&
options.entries[1] is StringConstant &&
(options.entries[1] as StringConstant).value ==
kResultTypeUsesPassedTypeArguments) {
return ParsedResultTypeByTypePragma(
(options.entries[0] as TypeLiteralConstant).type,
true,
);
}
throw "ERROR: Unsupported option to '$vmExactResultTypePragmaName' "
"pragma: $options";
case vmRecognizedPragmaName:
PragmaRecognizedType? type;
if (options is StringConstant) {
if (options.value == "asm-intrinsic") {
type = PragmaRecognizedType.AsmIntrinsic;
} else if (options.value == "graph-intrinsic") {
type = PragmaRecognizedType.GraphIntrinsic;
} else if (options.value == "other") {
type = PragmaRecognizedType.Other;
}
}
if (type == null) {
throw "ERROR: Unsupported option to '$vmRecognizedPragmaName' "
"pragma: $options";
}
return ParsedRecognized(type);
case vmDisableUnboxedParametersPragmaName:
return const ParsedDisableUnboxedParameters();
case vmKeepNamePragmaName:
return const ParsedKeepNamePragma();
case vmPlatformConstPragmaName:
return const ParsedPlatformConstPragma();
case vmPlatformConstIfPragmaName:
if (options is! BoolConstant) {
throw "ERROR: Non-boolean option to '$vmPlatformConstIfPragmaName' "
"pragma: $options";
}
return options.value ? const ParsedPlatformConstPragma() : null;
case vmFfiNative:
return const ParsedFfiNativePragma();
case wasmEntryPointPragmaName:
return const ParsedEntryPointPragma(PragmaEntryPointType.Default);
case wasmExportPragmaName:
// Exports are treated as entry points.
return const ParsedEntryPointPragma(PragmaEntryPointType.Default);
case dynModuleExtendablePragmaName:
return const ParsedEntryPointPragma(PragmaEntryPointType.Extendable);
case dynModuleImplicitlyExtendablePragmaName:
return const ParsedEntryPointPragma(
PragmaEntryPointType.ImplicitlyExtendable,
);
case dynModuleCanBeOverriddenPragmaName:
return const ParsedEntryPointPragma(
PragmaEntryPointType.CanBeOverridden,
);
case dynModuleCanBeUsedAsTypePragmaName:
return const ParsedEntryPointPragma(
PragmaEntryPointType.CanBeUsedAsType,
);
case dynModuleCallablePragmaName:
case dynModuleImplicitlyCallablePragmaName:
return getEntryPointTypeFromOptions(options, pragmaName);
case dynModuleDynamicallyCallablePragmaName:
case dynModuleImplicitlyDynamicallyCallablePragmaName:
return const ParsedEntryPointPragma(
PragmaEntryPointType.DynamicallyCallable,
);
case dynModuleEntryPointPragmaName:
return const ParsedDynModuleEntryPointPragma();
case vmSharedPragmaName:
return const ParsedVmSharedPragma();
case vmDeeplyImmutablePragmaName:
return const ParsedVmDeeplyImmutablePragma();
case vmInvisiblePragmaName:
return const ParsedVmInvisiblePragma();
default:
return null;
}
}
Iterable<R> parsedPragmas<R extends ParsedPragma>(
Iterable<Expression> annotations,
) => annotations.map(parsePragma).whereType<R>();
}
/// Helper methods for accessing pragmas.
/// Should be used on kernel AST nodes only after all constants are evaluated.
extension AnnotatblePragmaHelpers on Annotatable {
/// Returns true if this node is annotated with pragma [pragmaName].
bool hasPragma(String pragmaName, CoreTypes coreTypes) {
for (final annotation in annotations) {
switch (annotation) {
case ConstantExpression(:InstanceConstant constant)
when constant.classNode == coreTypes.pragmaClass &&
(constant.fieldValues[coreTypes.pragmaName.fieldReference]
as StringConstant)
.value ==
pragmaName:
return true;
case ConstantExpression(constant: UnevaluatedConstant()):
throw 'Error: unevaluated constant $annotation';
case ConstantExpression():
case InvalidExpression():
break;
default:
throw 'Error: non-constant annotation $annotation';
}
}
return false;
}
}
extension ClassPragmaHelpers on Class {
/// Returns true if this class is annotated with 'vm:deeply-immutable' pragma.
bool isDeeplyImmutable(CoreTypes coreTypes) =>
hasPragma(vmDeeplyImmutablePragmaName, coreTypes);
}
extension FieldPragmaHelpers on Field {
/// Returns true if this field is annotated with 'vm:shared' pragma.
bool isShared(CoreTypes coreTypes) =>
hasPragma(vmSharedPragmaName, coreTypes);
}
extension MemberPragmaHelpers on Member {
/// Returns true if this member is annotated with 'vm:ffi:native' pragma.
bool isFfiNative(CoreTypes coreTypes) => hasPragma(vmFfiNative, coreTypes);
/// Returns true if this member is annotated with 'vm:invisible' pragma.
bool isInvisible(CoreTypes coreTypes) =>
hasPragma(vmInvisiblePragmaName, coreTypes);
/// Returns true if this member is annotated with 'dyn-module:entry-point' pragma.
bool isDynModuleEntryPoint(CoreTypes coreTypes) =>
hasPragma(dynModuleEntryPointPragmaName, coreTypes);
}
extension FunctionDeclarationPragmaHelpers on FunctionDeclaration {
/// Returns true if this local function is annotated with 'vm:invisible' pragma.
bool isInvisible(CoreTypes coreTypes) =>
variable.hasPragma(vmInvisiblePragmaName, coreTypes);
}