blob: 7c6aeb1b555058ce8494470677fd8ef11d024278 [file] [log] [blame]
// Copyright (c) 2024, 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:_fe_analyzer_shared/src/type_inference/shared_inference_log.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/generated/resolver.dart';
final bool _assertionsEnabled = () {
bool enabled = false;
assert(enabled = true);
return enabled;
}();
/// The [InferenceLogWriter] currently being used by the analyzer, if inference
/// logging is active, otherwise `null`.
InferenceLogWriter? _inferenceLogWriter;
/// Expando storing a value `true` for each expression that has been passed to
/// [_InferenceLogWriterImpl.enterExpression].
///
/// This is used by [_InferenceLogWriterImpl.assertExpressionWasRecorded] to
/// verify that [_InferenceLogWriterImpl.enterExpression] was called when it
/// should be.
final _recordedExpressions = Expando<bool>();
/// Returns the [InferenceLogWriter] currently being used by the analyzer, if
/// inference logging is active, otherwise `null`.
InferenceLogWriter? get inferenceLogWriter => _inferenceLogWriter;
/// Starts up inference logging if appropriate.
///
/// Inference logging will be started up if either of the following conditions
/// are met:
/// - The [dump] parameter is `true` (in which case the log will immediately
/// start dumping events to standard output)
/// - Assertions are enabled.
void conditionallyStartInferenceLogging({bool dump = false}) {
assert(_inferenceLogWriter == null);
if (_assertionsEnabled || dump) {
var inferenceLogWriter = _inferenceLogWriter = _InferenceLogWriterImpl();
if (dump) {
inferenceLogWriter.dump();
}
}
}
/// Stops inference logging if it's been started.
void stopInferenceLogging() {
_inferenceLogWriter = null;
}
/// The [SharedInferenceLogWriter] interface, augmented with analyzer-specific
/// functionality.
abstract interface class InferenceLogWriter
implements SharedInferenceLogWriter<DartType> {
/// Checks that [enterExpression] was properly called for [expression].
///
/// This is called from [ResolverVisitor.dispatchExpression], to verify that
/// each expression's visit method property calls [enterExpression].
void assertExpressionWasRecorded(Expression expression);
}
/// The [SharedInferenceLogWriterImpl] implementation, augmented with
/// analyzer-specific functionality.
final class _InferenceLogWriterImpl
extends SharedInferenceLogWriterImpl<DartType>
implements InferenceLogWriter {
@override
void assertExpressionWasRecorded(Object expression) {
if (_recordedExpressions[expression] ?? false) return;
fail('failed to record ${describe(expression)}');
}
@override
void enterExpression(covariant Expression node, DartType contextType) {
checkCall(
method: 'enterExpression',
arguments: [node, contextType],
expectedNode: traceableAncestor(node));
super.enterExpression(node, contextType);
_recordedExpressions[node] = true;
}
@override
void enterExtensionOverride(
covariant ExtensionOverride node, DartType contextType) {
checkCall(
method: 'enterExtensionOverride',
arguments: [node, contextType],
expectedNode: traceableAncestor(node));
super.enterExtensionOverride(node, contextType);
_recordedExpressions[node] = true;
}
@override
void enterLValue(covariant Expression node) {
checkCall(
method: 'enterLValue',
arguments: [node],
expectedNode: traceableAncestor(node));
super.enterLValue(node);
}
/// Returns the nearest ancestor of [node] for which a call to `enter...`
/// should have been made.
///
/// This is used to verify proper nesting of `enter...` method calls.
AstNode? traceableAncestor(covariant AstNode node) {
for (var parent = node.parent;; parent = parent.parent) {
switch (parent) {
case null:
case Expression():
return parent;
default:
break;
}
}
}
}