Skip to content

[record_use] Dart API: Put non-const calls more front and center #2718

@dcharkes

Description

@dcharkes

The current API for retrieving recorded method calls is convenience-oriented, providing methods like constArgumentsFor() and hasNonConstArguments(). This leads users to forget to call hasNonConstArguments.

Proposed Solution:

Refactor the API to provide a single, powerful method that returns all recorded usages for a given method. The different kinds of calls should be represented by a sealed class hierarchy.

API Signature:

Iterable<CallReference> callsFor(Identifier id);

sealed class CallReference { ... }

// A direct call with arguments.
final class CallWithArguments extends CallReference {
  // ... properties for positional and named arguments
}

// A tear-off of the method.
final class CallTearOff extends CallReference { ... }

// A potential dynamic invocation.
final class DynamicInvocation extends CallReference { ... }

Example Usage

This example demonstrates a stricter approach where the presence of any non-const
usages is treated as a failure. It uses a custom Either type to return
either a list of errors (Left) or a list of the successfully extracted constant
values (Right).

import 'package:record_use/record_use.dart';

// A simple Either type to represent success (Right) or failure (Left).
sealed class Either<L, R> {}

class Left<L, R> extends Either<L, R> {
  final L value;
  Left(this.value);
}

class Right<L, R> extends Either<L, R> {
  final R value;
  Right(this.value);
}

Either<List<String>, List<Constant>> processMethodUsages(
    Recordings recordings, Identifier methodId) {
  final calls = recordings.callsFor(methodId);
  final constValues = <Constant>[];
  final errors = <String>[];

  for (final call in calls) {
    switch (call) {
      case CallWithArguments(
          location: final loc,
          namedArguments: {'myArg': final Constant constArg}
        ):
        constValues.add(constArg);
        break;

      case CallTearOff(location: final loc):
        errors.add('Error: Found a tear-off at ${loc.uri}:${loc.line}.');
        break;

      case DynamicInvocation(location: final loc):
        errors.add('Error: Found a dynamic invocation at ${loc.uri}:${loc.line}.');
        break;

      case CallWithArguments(
          location: final loc,
          namedArguments: {'myArg': null}
        ):
        errors.add(
          'Error: Call at ${loc.uri}:${loc.line} had a non-constant \'myArg\'.',
        );
        break;

      case CallWithArguments(location: final loc):
        errors.add(
          'Error: Call at ${loc.uri}:${loc.line} did not provide the \'myArg\' argument.',
        );
        break;
    }
  }

  if (errors.isNotEmpty) {
    return Left(errors);
  }
  return Right(constValues);
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status

    Todo

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions