Skip to content

[macros] Add dart_model exploration code. #3851

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 30, 2024
Merged
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
22 changes: 22 additions & 0 deletions working/macros/dart_model/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# `dart_model` exploration

Code exploring
[a query-like API](https://github.com/dart-lang/language/issues/3706) for
macros, in particular with regard to incremental build performance and
convenience for macro authors.

_This code will be deleted/archived, do not use it for anything!_

Packages:

`dart_model` is a standalone data model, the input to a macro;\
`dart_model_analyzer_service` serves `dart_model` queries using the analyzer
as a library;\
`dart_model_repl` is a REPL that can issue queries and watch code for changes\
`macro_client` is for writing "macros";\
`macro_host` hosts a set of "macros" running against a codebase;\
`macro_protocol` is how "macros" communicate with their host;\
`testing` is for test "macros" and experiments with them.

The "macros" referred to in this exploration are independent of the in-progress
macro implementation, hence the "scare quotes".
91 changes: 91 additions & 0 deletions working/macros/dart_model/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
linter:
rules:
# From package:lints/core.yaml
- avoid_empty_else
- avoid_relative_lib_imports
- avoid_shadowing_type_parameters
- avoid_types_as_parameter_names
- await_only_futures
- camel_case_extensions
- camel_case_types
- collection_methods_unrelated_type
- curly_braces_in_flow_control_structures
- dangling_library_doc_comments
- depend_on_referenced_packages
- empty_catches
- file_names
- hash_and_equals
- implicit_call_tearoffs
- library_annotations
- no_duplicate_case_values
- no_wildcard_variable_uses
- non_constant_identifier_names
- null_check_on_nullable_type_parameter
- prefer_generic_function_type_aliases
- prefer_is_empty
- prefer_is_not_empty
- prefer_iterable_whereType
- prefer_typing_uninitialized_variables
- provide_deprecation_message
- secure_pubspec_urls
- type_literal_in_constant_pattern
- unnecessary_overrides
- unrelated_type_equality_checks
- use_string_in_part_of_directives
- valid_regexps
- void_checks
# From package:lints/recommended.yaml
- annotate_overrides
- avoid_function_literals_in_foreach_calls
- avoid_init_to_null
- avoid_null_checks_in_equality_operators
- avoid_renaming_method_parameters
- avoid_return_types_on_setters
- avoid_returning_null_for_void
- avoid_single_cascade_in_expression_statements
- constant_identifier_names
- control_flow_in_finally
- empty_constructor_bodies
- empty_statements
- exhaustive_cases
- implementation_imports
- library_prefixes
- library_private_types_in_public_api
- no_leading_underscores_for_library_prefixes
- no_leading_underscores_for_local_identifiers
- null_closures
- overridden_fields
- package_names
- prefer_adjacent_string_concatenation
- prefer_collection_literals
- prefer_conditional_assignment
- prefer_contains
- prefer_final_fields
- prefer_for_elements_to_map_fromIterable
- prefer_function_declarations_over_variables
- prefer_if_null_operators
- prefer_initializing_formals
- prefer_inlined_adds
- prefer_interpolation_to_compose_strings
- prefer_is_not_operator
- prefer_null_aware_operators
- prefer_spread_collections
- recursive_getters
- slash_for_doc_comments
- type_init_formals
- unnecessary_brace_in_string_interps
- unnecessary_const
- unnecessary_constructor_name
- unnecessary_getters_setters
- unnecessary_late
- unnecessary_new
- unnecessary_null_aware_assignments
- unnecessary_null_in_if_null_operators
- unnecessary_nullable_for_final_variable_declarations
- unnecessary_string_escapes
- unnecessary_string_interpolations
- unnecessary_this
- unnecessary_to_list_in_spreads
- use_function_type_syntax_for_parameters
- use_rethrow_when_possible
- use_super_parameters
106 changes: 106 additions & 0 deletions working/macros/dart_model/dart_model/lib/delta.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// 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 'dart:convert';

import 'package:collection/collection.dart';

import 'model.dart';

extension type Update.fromJson(List<Object?> node) {
Update({
required Path path,
required Object? value,
}) : this.fromJson([path, value]);

Path get path => node[0] as Path;
Object? get value => node[1];
}

extension type Removal.fromJson(List<Object?> node) {
Removal({
required Path path,
}) : this.fromJson(path.path);

Path get path => node as Path;
}

extension type Delta.fromJson(Map<String, Object?> node) {
Delta({
List<Update>? updates,
List<Removal>? removals,
}) : this.fromJson({
'updates': updates ?? [],
'removals': removals ?? [],
});

static Delta compute(Model previous, Model current) {
final updates = <Update>[];
final removals = <Removal>[];
_compute(previous, current, Path([]), updates, removals);
return Delta(updates: updates, removals: removals);
}

static void _compute(Model previous, Model current, Path path,
List<Update> updates, List<Removal> removals) {
for (final key
in previous.node.keys.followedBy(current.node.keys).toSet()) {
final keyIsInPrevious = previous.node.containsKey(key);
final keyIsInCurrent = current.node.containsKey(key);

if (keyIsInPrevious && !keyIsInCurrent) {
removals.add(Removal(path: path.followedByOne(key)));
} else if (keyIsInPrevious && keyIsInCurrent) {
// It's either the same or a change.
final previousValue = previous.node[key]!;
final currentValue = current.node[key]!;

if (currentValue is Map<String, Object?>) {
if (previousValue is Map<String, Object?>) {
_compute(previousValue as Model, currentValue as Model,
path.followedByOne(key), updates, removals);
} else {
updates.add(
Update(path: path.followedByOne(key), value: currentValue));
}
} else if (currentValue is String) {
if (previousValue is! String || previousValue != currentValue) {
updates.add(
Update(path: path.followedByOne(key), value: currentValue));
}
} else if (currentValue is List) {
if (previousValue is! List ||
!const DeepCollectionEquality()
.equals(previousValue, currentValue)) {
updates.add(
Update(path: path.followedByOne(key), value: currentValue));
}
} else {
throw 'Not sure what to do: $previousValue $currentValue';
}
} else {
// It's new.
updates.add(
Update(path: path.followedByOne(key), value: current.node[key]));
}
}
}

bool get isEmpty => updates.isEmpty && removals.isEmpty;

List<Update> get updates => (node['updates'] as List).cast();

List<Removal> get removals => (node['removals'] as List).cast();

String prettyPrint() => const JsonEncoder.withIndent(' ').convert(node);

void update(Model previous) {
for (final update in updates) {
previous.updateAtPath(update.path, update.value);
}
for (final removal in removals) {
previous.removeAtPath(removal.path);
}
}
}
Loading
Loading