mirror of
https://github.com/oxc-project/oxc.git
synced 2026-09-14 19:36:11 +08:00
f2e4ab0921
Separate React Compiler emission from lint analysis so oxlint can dead-strip compiler codegen. This reduces the stripped oxlint binary by 145 KiB (1.21%). AI-assisted with Codex.
534 lines
23 KiB
Rust
534 lines
23 KiB
Rust
//! Snapshot tests over the upstream React Compiler fixture corpus.
|
|
//!
|
|
//! Each input under `fixtures/` (copied from upstream
|
|
//! `babel-plugin-react-compiler/src/__tests__/fixtures/compiler`) is parsed,
|
|
//! analysed, and run through [`oxc_react_compiler::compile`]; the compiled output
|
|
//! plus diagnostics are snapshotted with `insta` into `tests/snapshots/`.
|
|
//!
|
|
//! Per-fixture options come from the first-line `// @directive` pragmas, mirroring
|
|
//! the upstream `snap` runner: the base config is `compilationMode: all` /
|
|
//! `panicThreshold: all_errors`, then each recognised directive overrides a
|
|
//! `PluginOptions` or `EnvironmentConfig` field. Unmodelled directives are ignored,
|
|
//! as upstream ignores unknown keys.
|
|
//!
|
|
//! The snapshots are oxc's *own* golden output, so any change in compiler behaviour
|
|
//! surfaces as a diff. Regenerate with `cargo insta accept` after reviewing.
|
|
|
|
use std::{
|
|
ffi::OsStr,
|
|
fs,
|
|
path::{Component, Path, PathBuf},
|
|
};
|
|
|
|
use convert_case::{Case, Casing};
|
|
use cow_utils::CowUtils;
|
|
use oxc_allocator::Allocator;
|
|
use oxc_codegen::Codegen;
|
|
use oxc_parser::Parser;
|
|
use oxc_react_compiler::{
|
|
AliasingEffectConfig, AliasingSignatureConfig, BuiltInTypeRef, CompilationMode, CompileResult,
|
|
CompilerOutputMode, DynamicGatingConfig, Effect, EnvironmentConfig, ExhaustiveEffectDepsMode,
|
|
ExternalFunctionConfig, FunctionTypeConfig, FxIndexMap, GatingConfig, HookTypeConfig,
|
|
InstrumentationConfig, ObjectTypeConfig, PanicThreshold, PluginOptions, TypeConfig,
|
|
TypeReferenceConfig, ValueKind, ValueReason, compile, lint,
|
|
};
|
|
use oxc_semantic::SemanticBuilder;
|
|
use oxc_span::SourceType;
|
|
|
|
#[test]
|
|
fn snapshots() {
|
|
let fixtures = concat!(env!("CARGO_MANIFEST_DIR"), "/fixtures");
|
|
insta::glob!(fixtures, "**/*.{js,cjs,mjs,ts,cts,mts,jsx,tsx}", |path| {
|
|
let source = fs::read_to_string(path).unwrap();
|
|
let source = normalize_newlines(&source);
|
|
let snapshot = run_fixture(&source);
|
|
insta::with_settings!({ prepend_module_to_snapshot => false, snapshot_suffix => "", omit_expression => true }, {
|
|
insta::assert_snapshot!(snapshot_name(path), snapshot);
|
|
});
|
|
});
|
|
}
|
|
|
|
fn normalize_newlines(source: &str) -> String {
|
|
source.cow_replace("\r\n", "\n").cow_replace('\r', "\n").into_owned()
|
|
}
|
|
|
|
/// Parse, analyse, compile, and render the compiled program + diagnostics, plus any
|
|
/// divergence between the read-only `lint` entry point and `compile`.
|
|
fn run_fixture(source: &str) -> String {
|
|
let (source_type, options) = parse_pragma(source);
|
|
// In lint output mode the compiler validates without rewriting the program, so
|
|
// `changed` is always false. Upstream's `snap` runner still emits the (unmodified)
|
|
// code in that mode alongside the reported findings, so mirror that here rather
|
|
// than collapsing to "No changes." — otherwise every lint fixture would look like a
|
|
// bail-out even though the compiler ran to completion.
|
|
let lint_mode = CompilerOutputMode::from_opts(&options) == CompilerOutputMode::Lint;
|
|
|
|
let allocator = Allocator::default();
|
|
let parsed = Parser::new(&allocator, source, source_type).parse();
|
|
let mut program = parsed.program;
|
|
|
|
let mut out = String::new();
|
|
// Surface parse failures rather than silently compiling a recovered/dummy AST.
|
|
push_diagnostics(&mut out, "Parse errors", parsed.diagnostics.as_slice());
|
|
|
|
// `compile` and `lint` both borrow a `Semantic` built from the pristine program;
|
|
// scope the borrow so it ends before the output rewrites the program. `compile`
|
|
// runs first on the untouched allocator so its output stays byte-identical to a
|
|
// compile-only run; `lint` then re-runs the same pipeline read-only so we can
|
|
// cross-check its diagnostics against `compile`'s below.
|
|
let (output, diagnostics, fatal, lint_result) = {
|
|
let semantic = SemanticBuilder::new().with_build_nodes(true).build(&program).semantic;
|
|
let (output, diagnostics, fatal) =
|
|
match compile(&program, &semantic, &allocator, options.clone()) {
|
|
CompileResult::Success { output, diagnostics } => (output, diagnostics, false),
|
|
CompileResult::Fatal { diagnostics } => (None, diagnostics, true),
|
|
};
|
|
let lint_result = lint(&program, &semantic, &allocator, options);
|
|
(output, diagnostics, fatal, lint_result)
|
|
};
|
|
let changed = output.is_some();
|
|
if let Some(output) = output {
|
|
output.transform(&mut program);
|
|
}
|
|
|
|
push_diagnostics(&mut out, "Diagnostics", diagnostics.as_slice());
|
|
// Mirror the upstream `snap` runner, which always re-emits the program as
|
|
// `## Code` unless a hard error turns the output into `## Error`. So when the
|
|
// compiler cleanly declines to change anything (e.g. `@expectNothingCompiled`,
|
|
// or a file with no React-like functions), or when a lint-mode run reports
|
|
// findings without rewriting the program, echo the reprinted source rather than
|
|
// the `No changes.` marker. The marker is kept only when a non-lint run is
|
|
// fatal (a parse failure or an escalated compile diagnostic), where upstream
|
|
// emits no code — and echoing a parse-recovered AST would be misleading.
|
|
let clean = parsed.diagnostics.is_empty() && !fatal;
|
|
if changed || clean || lint_mode {
|
|
out.push_str(&Codegen::new().build(&program).code);
|
|
} else {
|
|
out.push_str("No changes.");
|
|
}
|
|
|
|
// Cross-check the read-only `lint` entry point against `compile`. Both funnel
|
|
// through the same pipeline, so their diagnostics are identical for almost
|
|
// every fixture. They diverge only where the pipeline gates a validation on lint
|
|
// output mode: `@validateNoSetStateInEffects` / `@validateNoJSXInTryStatements` /
|
|
// `@validateStaticComponents` / `@validateNoDerivedComputationsInEffectsExp` fixtures
|
|
// gain lint-only findings, while `@enableEmitHookGuards` conversely errors only when
|
|
// emitting. Surface any divergence in the snapshot so it stays reviewed rather than
|
|
// drifting silently.
|
|
let transform_body = diagnostics_body(diagnostics.as_slice());
|
|
// `lint` wraps each compiler diagnostic with its category for routing. Compare
|
|
// only the wrapped diagnostic so lint and transform modes remain byte-identical.
|
|
let lint_diagnostics = lint_result
|
|
.diagnostics
|
|
.iter()
|
|
.map(|diagnostic| diagnostic.diagnostic.clone())
|
|
.collect::<Vec<_>>();
|
|
let lint_body = diagnostics_body(&lint_diagnostics);
|
|
// A fatal compile may originate from codegen, which lint intentionally does not
|
|
// run, and may replace diagnostics accumulated by earlier passes. Successful
|
|
// compilation still has to agree with lint on diagnostics and fatality.
|
|
if !fatal && lint_body != transform_body {
|
|
out.push_str("\n\nLint-mode diagnostics (differ from transform):\n\n");
|
|
out.push_str(if lint_body.is_empty() { "(none)\n" } else { &lint_body });
|
|
}
|
|
if !fatal {
|
|
assert_eq!(lint_result.fatal, fatal, "lint and compile fatality diverged");
|
|
}
|
|
out
|
|
}
|
|
|
|
/// Append a `"{label}:\n\n{diag}\n…\n"` section, or nothing when there are none.
|
|
fn push_diagnostics(out: &mut String, label: &str, diagnostics: &[impl std::fmt::Debug]) {
|
|
let body = diagnostics_body(diagnostics);
|
|
if body.is_empty() {
|
|
return;
|
|
}
|
|
out.push_str(label);
|
|
out.push_str(":\n\n");
|
|
out.push_str(&body);
|
|
out.push('\n');
|
|
}
|
|
|
|
/// Render diagnostics as one `{diag:?}` line each, or `""` when there are none.
|
|
fn diagnostics_body(diagnostics: &[impl std::fmt::Debug]) -> String {
|
|
let mut body = String::new();
|
|
for diagnostic in diagnostics {
|
|
body.push_str(&format!("{diagnostic:?}\n"));
|
|
}
|
|
body
|
|
}
|
|
|
|
/// Snapshot name = fixture path under `fixtures/`, extension dropped and path
|
|
/// separators replaced with `__`, so nested fixtures with the same basename
|
|
/// don't collide.
|
|
fn snapshot_name(path: &Path) -> String {
|
|
let components = path.components().collect::<Vec<_>>();
|
|
let start = components
|
|
.iter()
|
|
.rposition(|component| {
|
|
matches!(component, Component::Normal(part) if *part == OsStr::new("fixtures"))
|
|
})
|
|
.map_or(0, |index| index + 1);
|
|
|
|
let mut rel = PathBuf::new();
|
|
for component in &components[start..] {
|
|
rel.push(component.as_os_str());
|
|
}
|
|
rel.set_extension("");
|
|
|
|
rel.components()
|
|
.filter_map(|component| match component {
|
|
Component::Normal(part) => Some(part.to_string_lossy()),
|
|
_ => None,
|
|
})
|
|
.collect::<Vec<_>>()
|
|
.join("__")
|
|
}
|
|
|
|
/// Build the per-fixture `SourceType` + `PluginOptions` from the first-line pragmas.
|
|
fn parse_pragma(source: &str) -> (SourceType, PluginOptions) {
|
|
// Upstream `snap` defaults: compile everything, surface every error.
|
|
let mut options = PluginOptions {
|
|
compilation_mode: CompilationMode::All,
|
|
panic_threshold: PanicThreshold::AllErrors,
|
|
..PluginOptions::default()
|
|
};
|
|
// Mirror the snap runner's test `moduleTypeProvider`; unlisted modules fall back to
|
|
// oxc's default provider, so only the `error.invalid-{type-provider,known-incompatible}-*`
|
|
// fixtures (which import these magic modules) are affected.
|
|
options.environment.module_type_provider = Some(test_module_type_provider());
|
|
|
|
let mut is_script = false;
|
|
// Fixture headers are normalised to the canonical `@key:value` / bare `@key` shape,
|
|
// so each `@`-entry either splits at its first `:` into key + raw value (values keep
|
|
// internal spaces, e.g. `["use todo memo"]`) or is a bare flag.
|
|
for entry in source.lines().next().unwrap_or("").split('@').skip(1) {
|
|
let entry = entry.trim();
|
|
let (key, value) = match entry.split_once(':') {
|
|
Some((key, value)) => (key, Some(value)),
|
|
None => (entry, None),
|
|
};
|
|
match key {
|
|
"script" => is_script = true,
|
|
"compilationMode" => {
|
|
if let Some(v) = value.and_then(|v| unquote(v).parse().ok()) {
|
|
options.compilation_mode = v;
|
|
}
|
|
}
|
|
"panicThreshold" => {
|
|
if let Some(v) = value.and_then(|v| unquote(v).parse().ok()) {
|
|
options.panic_threshold = v;
|
|
}
|
|
}
|
|
"outputMode" => options.output_mode = value.and_then(|v| unquote(v).parse().ok()),
|
|
// Fixed test default (upstream `testComplexPluginOptionDefaults`).
|
|
"gating" => {
|
|
options.gating = Some(GatingConfig {
|
|
source: "ReactForgetFeatureFlag".to_string(),
|
|
import_specifier_name: "isForgetEnabled_Fixtures".to_string(),
|
|
});
|
|
}
|
|
"dynamicGating" => {
|
|
if let Some(source) = value.and_then(json_source) {
|
|
options.dynamic_gating = Some(DynamicGatingConfig { source });
|
|
}
|
|
}
|
|
"customOptOutDirectives" => {
|
|
if let Some(v) = value {
|
|
options.custom_opt_out_directives = Some(json_strings(v));
|
|
}
|
|
}
|
|
other => set_environment_directive(&mut options.environment, other, value),
|
|
}
|
|
}
|
|
|
|
// Mirror the snap runner (`packages/snap/src/compiler.ts`): the fixture corpus runs
|
|
// with `validatePreserveExistingMemoizationGuarantees` OFF by default — most fixtures
|
|
// care about compilation output, not whether manual memoization is preserved — and it
|
|
// is turned on only when the fixture's first line opts in via the directive. Snap keys
|
|
// off substring presence alone (ignoring any `:value`), so replicate that exactly and
|
|
// let it override whatever the directive loop parsed. Without this, fixtures that set
|
|
// `@enablePreserveExistingMemoizationGuarantees:false` still hit `ValidatePreservedManual-
|
|
// Memoization` and bail with a spurious "memoization could not be preserved" error.
|
|
options.environment.validate_preserve_existing_memoization_guarantees = source
|
|
.lines()
|
|
.next()
|
|
.unwrap_or("")
|
|
.contains("@validatePreserveExistingMemoizationGuarantees");
|
|
|
|
// Upstream parses every (non-Flow) fixture with the TypeScript + JSX plugins,
|
|
// regardless of extension, and as a module unless `@script` — so injected runtime
|
|
// imports are `import`, not `require`. Flow fixtures are excluded (no oxc parser).
|
|
let source_type = if is_script {
|
|
SourceType::tsx().with_script(true)
|
|
} else {
|
|
SourceType::tsx().with_module(true)
|
|
};
|
|
(source_type, options)
|
|
}
|
|
|
|
/// Strip surrounding double quotes: `"annotation"` → `annotation`.
|
|
fn unquote(value: &str) -> String {
|
|
value.strip_prefix('"').and_then(|v| v.strip_suffix('"')).unwrap_or(value).to_string()
|
|
}
|
|
|
|
/// Apply a non-boolean `EnvironmentConfig` directive, delegating plain booleans to
|
|
/// [`set_environment_bool`]. Unmodelled keys are ignored, matching the upstream runner.
|
|
fn set_environment_directive(env: &mut EnvironmentConfig, camel_key: &str, value: Option<&str>) {
|
|
// `throwUnknownException__testonly`'s double underscore won't fold via convert_case.
|
|
let snake = if camel_key == "throwUnknownException__testonly" {
|
|
"throw_unknown_exception_testonly".to_string()
|
|
} else {
|
|
camel_key.to_case(Case::Snake)
|
|
};
|
|
match snake.as_str() {
|
|
"validate_exhaustive_effect_dependencies" => {
|
|
env.validate_exhaustive_effect_dependencies = match value.map(unquote).as_deref() {
|
|
Some("missing-only") => ExhaustiveEffectDepsMode::MissingOnly,
|
|
Some("extra-only") => ExhaustiveEffectDepsMode::ExtraOnly,
|
|
_ => ExhaustiveEffectDepsMode::All,
|
|
};
|
|
}
|
|
// Allow/block lists — a bare directive is `Some([])` (upstream
|
|
// `testComplexConfigDefaults`), else the parsed JSON array.
|
|
"validate_no_capitalized_calls" => {
|
|
env.validate_no_capitalized_calls = Some(value.map(json_strings).unwrap_or_default());
|
|
}
|
|
"validate_blocklisted_imports" => {
|
|
env.validate_blocklisted_imports = Some(value.map(json_strings).unwrap_or_default());
|
|
}
|
|
// Upstream keeps only the macro name (before the first `.`).
|
|
"custom_macros" => {
|
|
if let Some(v) = value {
|
|
let name = unquote(v).split('.').next().unwrap_or_default().to_string();
|
|
env.custom_macros = Some(vec![name]);
|
|
}
|
|
}
|
|
// Configs with fixed test defaults (upstream `testComplexConfigDefaults`).
|
|
"enable_emit_instrument_forget" => {
|
|
env.enable_emit_instrument_forget = Some(InstrumentationConfig {
|
|
fn_: external_fn("react-compiler-runtime", "useRenderCounter"),
|
|
gating: Some(external_fn("react-compiler-runtime", "shouldInstrument")),
|
|
global_gating: Some("DEV".to_string()),
|
|
});
|
|
}
|
|
"enable_emit_hook_guards" => {
|
|
env.enable_emit_hook_guards =
|
|
Some(external_fn("react-compiler-runtime", "$dispatcherGuard"));
|
|
}
|
|
_ => set_environment_bool(env, &snake, value != Some("false")),
|
|
}
|
|
}
|
|
|
|
/// Set a boolean `EnvironmentConfig` field whose name equals the snake-case directive
|
|
/// key. A bare `@key` (or `@key:true`) sets it; `@key:false` clears it.
|
|
fn set_environment_bool(env: &mut EnvironmentConfig, snake: &str, on: bool) {
|
|
macro_rules! fields {
|
|
($($field:ident)*) => {
|
|
match snake {
|
|
$(stringify!($field) => env.$field = on,)*
|
|
_ => {}
|
|
}
|
|
};
|
|
}
|
|
fields! {
|
|
enable_preserve_existing_memoization_guarantees
|
|
validate_preserve_existing_memoization_guarantees
|
|
validate_exhaustive_memoization_dependencies
|
|
enable_optional_dependencies
|
|
enable_name_anonymous_functions
|
|
validate_hooks_usage
|
|
validate_ref_access_during_render
|
|
validate_no_set_state_in_render
|
|
enable_use_keyed_state
|
|
validate_no_set_state_in_effects
|
|
validate_no_derived_computations_in_effects
|
|
validate_no_derived_computations_in_effects_exp
|
|
validate_no_jsx_in_try_statements
|
|
validate_static_components
|
|
validate_source_locations
|
|
validate_no_impure_functions_in_render
|
|
validate_no_freezing_known_mutable_functions
|
|
enable_assume_hooks_follow_rules_of_react
|
|
enable_transitively_freeze_function_expressions
|
|
enable_function_outlining
|
|
enable_jsx_outlining
|
|
assert_valid_mutable_ranges
|
|
throw_unknown_exception_testonly
|
|
enable_custom_type_definition_for_reanimated
|
|
enable_treat_ref_like_identifiers_as_refs
|
|
enable_treat_set_identifiers_as_state_setters
|
|
validate_no_void_use_memo
|
|
enable_allow_set_state_from_refs_in_effects
|
|
enable_verbose_no_set_state_in_effect
|
|
enable_forest
|
|
}
|
|
}
|
|
|
|
fn external_fn(source: &str, import_specifier_name: &str) -> ExternalFunctionConfig {
|
|
ExternalFunctionConfig {
|
|
source: source.to_string(),
|
|
import_specifier_name: import_specifier_name.to_string(),
|
|
}
|
|
}
|
|
|
|
/// The double-quoted strings inside a JSON literal, in order:
|
|
/// `["a","b"]` → `["a", "b"]`, `{"source":"x"}` → `["source", "x"]`.
|
|
fn json_strings(value: &str) -> Vec<String> {
|
|
value.split('"').skip(1).step_by(2).map(str::to_string).collect()
|
|
}
|
|
|
|
/// The `source` field of a JSON object literal like `{"source":"shared-runtime"}`.
|
|
fn json_source(value: &str) -> Option<String> {
|
|
let parts = json_strings(value);
|
|
parts.iter().position(|s| s == "source").and_then(|i| parts.get(i + 1).cloned())
|
|
}
|
|
|
|
/// The upstream snap runner's test `moduleTypeProvider` (`shared-runtime-type-provider`),
|
|
/// limited to the magic modules the corpus imports to exercise type-config /
|
|
/// known-incompatible-library validation. These configs are intentionally invalid or
|
|
/// flagged, so the `error.invalid-type-provider-*` / `error.invalid-known-incompatible-*`
|
|
/// fixtures produce diagnostics instead of compiling.
|
|
fn test_module_type_provider() -> FxIndexMap<String, TypeConfig> {
|
|
FxIndexMap::from_iter([
|
|
(
|
|
"ReactCompilerKnownIncompatibleTest".to_string(),
|
|
object([
|
|
(
|
|
"useKnownIncompatible",
|
|
hook(
|
|
type_ref(),
|
|
Some(Vec::new()),
|
|
incompat("useKnownIncompatible is known to be incompatible"),
|
|
),
|
|
),
|
|
(
|
|
"useKnownIncompatibleIndirect",
|
|
hook(
|
|
object([(
|
|
"incompatible",
|
|
incompatible_fn(
|
|
"useKnownIncompatibleIndirect returns an incompatible() function that is known incompatible",
|
|
),
|
|
)]),
|
|
Some(Vec::new()),
|
|
None,
|
|
),
|
|
),
|
|
(
|
|
"knownIncompatible",
|
|
incompatible_fn("useKnownIncompatible is known to be incompatible"),
|
|
),
|
|
(
|
|
"knownIncompatibleAliasing",
|
|
incompatible_aliasing_fn(
|
|
"knownIncompatibleAliasing is known to be incompatible",
|
|
),
|
|
),
|
|
]),
|
|
),
|
|
(
|
|
"ReactCompilerTest".to_string(),
|
|
object([
|
|
("useHookNotTypedAsHook", type_ref()),
|
|
("notAhookTypedAsHook", hook(type_ref(), None, None)),
|
|
]),
|
|
),
|
|
(
|
|
"ReactCompilerPureTagTest".to_string(),
|
|
object([(
|
|
"tag",
|
|
TypeConfig::Function(FunctionTypeConfig {
|
|
positional_params: Vec::new(),
|
|
rest_param: Some(Effect::Read),
|
|
callee_effect: Effect::Read,
|
|
return_type: Box::new(TypeConfig::TypeReference(TypeReferenceConfig {
|
|
name: BuiltInTypeRef::Any,
|
|
})),
|
|
return_value_kind: ValueKind::Mutable,
|
|
no_alias: None,
|
|
mutable_only_if_operands_are_mutable: None,
|
|
impure: Some(false),
|
|
canonical_name: None,
|
|
aliasing: None,
|
|
known_incompatible: None,
|
|
}),
|
|
)]),
|
|
),
|
|
("useDefaultExportNotTypedAsHook".to_string(), object([("default", type_ref())])),
|
|
])
|
|
}
|
|
|
|
fn object(properties: impl IntoIterator<Item = (&'static str, TypeConfig)>) -> TypeConfig {
|
|
let properties = properties.into_iter().map(|(name, config)| (name.to_string(), config));
|
|
TypeConfig::Object(ObjectTypeConfig { properties: Some(properties.collect()) })
|
|
}
|
|
|
|
fn type_ref() -> TypeConfig {
|
|
TypeConfig::TypeReference(TypeReferenceConfig { name: BuiltInTypeRef::Any })
|
|
}
|
|
|
|
fn hook(
|
|
return_type: TypeConfig,
|
|
positional_params: Option<Vec<Effect>>,
|
|
known_incompatible: Option<String>,
|
|
) -> TypeConfig {
|
|
let rest_param = positional_params.as_ref().map(|_| Effect::Read);
|
|
TypeConfig::Hook(HookTypeConfig {
|
|
positional_params,
|
|
rest_param,
|
|
return_type: Box::new(return_type),
|
|
return_value_kind: None,
|
|
no_alias: None,
|
|
aliasing: None,
|
|
known_incompatible,
|
|
})
|
|
}
|
|
|
|
fn incompatible_fn(message: &str) -> TypeConfig {
|
|
TypeConfig::Function(FunctionTypeConfig {
|
|
positional_params: Vec::new(),
|
|
rest_param: Some(Effect::Read),
|
|
callee_effect: Effect::Read,
|
|
return_type: Box::new(type_ref()),
|
|
return_value_kind: ValueKind::Mutable,
|
|
no_alias: None,
|
|
mutable_only_if_operands_are_mutable: None,
|
|
impure: None,
|
|
canonical_name: None,
|
|
aliasing: None,
|
|
known_incompatible: Some(message.to_string()),
|
|
})
|
|
}
|
|
|
|
fn incompatible_aliasing_fn(message: &str) -> TypeConfig {
|
|
TypeConfig::Function(FunctionTypeConfig {
|
|
positional_params: Vec::new(),
|
|
rest_param: Some(Effect::Read),
|
|
callee_effect: Effect::Read,
|
|
return_type: Box::new(type_ref()),
|
|
return_value_kind: ValueKind::Mutable,
|
|
no_alias: None,
|
|
mutable_only_if_operands_are_mutable: None,
|
|
impure: None,
|
|
canonical_name: None,
|
|
aliasing: Some(AliasingSignatureConfig {
|
|
receiver: "@receiver",
|
|
params: &[],
|
|
rest: Some("@rest"),
|
|
returns: "@returns",
|
|
temporaries: &[],
|
|
effects: &[AliasingEffectConfig::Create {
|
|
into: "@returns",
|
|
value: ValueKind::Mutable,
|
|
reason: ValueReason::KnownReturnSignature,
|
|
}],
|
|
}),
|
|
known_incompatible: Some(message.to_string()),
|
|
})
|
|
}
|
|
|
|
fn incompat(message: &str) -> Option<String> {
|
|
Some(message.to_string())
|
|
}
|