build icon indicating copy to clipboard operation
build copied to clipboard

"[SEVERE] type 'YamlList' is not a subtype of type 'List<String>' in type cast" when overriding builder options

Open wujek-srujek opened this issue 3 years ago • 5 comments

I have a builder defined like this:

builders:
  my_builder:
    import: my_package:my_builder/builder.dart
    builder_factories:
      - myBuilder
    build_extensions:
      $package$:
        - lib/generated/my_file.dart
    auto_apply: dependents
    build_to: source
    defaults:
      options:
        dirs:
          - dir_1

and then in a different project I use this bilder and overwrite its options to be:

targets:
  $default:
    builders:
      my_package:my_builder:
        options:
          dirs:
            - dir_a

The builder factory is like this:

Builder myuilder(BuilderOptions options) {
  return MyBuilder(options.config['dirs']! as List<String>);
}

It works when I don't override the dirs configuration in a target, but when I do I get

SEVERE] 
type 'YamlList' is not a subtype of type 'List<String>' in type cast

When I debug the factory it the value really is a YamlList.

Is this expected? Why does it behave differently with and without the options being overridden in a dependent package?

wujek-srujek avatar May 09 '22 21:05 wujek-srujek

This looks expected to me, we are parsing things as yaml, which allows for values other than strings in lists. It isn't safe to cast this directly to a List<String> here, you need to either .cast<String>() or create a new list (using List.from etc).

I don't know why this would work when you aren't overriding it, unless the builder was not applied at all? I would expect the same thing to happen with the default options.

jakemac53 avatar May 09 '22 21:05 jakemac53

It was auto applied and I can debug both situation and without the options being overwritten I get a List<String> hence the cast was working; when I overwrite the options I get a YamlList instead. Yeah, I changed the code to be

(options.config['dirs']! as List<dynamic>).cast<String>()

and it works in both cases. But the change in behavior is pretty strange.

wujek-srujek avatar May 09 '22 21:05 wujek-srujek

I'm very surprised you'd ever get a List<String> where that cast would succeed, can you elaborate on the setup to see that behavior?

natebosch avatar May 09 '22 23:05 natebosch

Sure.

$ dart --version
Dart SDK version: 2.16.2 (stable) (Tue Mar 22 13:15:13 2022 +0100) on "macos_x64"

Here is the code:

import 'package:build/build.dart';

class MyBuilder implements Builder {
  final List<String> dirs;

  MyBuilder(this.dirs);

  @override
  final buildExtensions = const {
    r'$package$': ['lib/generated/my_file.dart'],
  };

  @override
  Future<void> build(BuildStep buildStep) async {
    print('$dirs');
  }
}
import 'package:build/build.dart';

import 'src/my_builder.dart';

Builder myBuilder(BuilderOptions options) {
  print(options.config['dirs'].runtimeType); // prints List<String>
  return MyBuilder(
    options.config['dirs']! as List<String>,
  );
}

builders:
  my_builder:
    import: package:my_package/builder.dart
    builder_factories:
      - myBuilder
    build_extensions:
      $package$:
        - lib/generated/my_file.dart
    auto_apply: root_package
    build_to: source
    defaults:
      options:
        dirs:
          - dir_a

# targets:
#   $default:
#     builders:
#       my_package:my_builder:
#         options:
#           dirs:
#             - dir_a
name: my_package
version: 1.0.0+1

environment:
  sdk: '>=2.16.2 <3.0.0'

dependencies:
  build: ^2.3.0
  build_config: ^1.0.0
  build_runner: ^2.1.0

This works file. Uncomment the targets section in build.yaml and it results in the error mentioned above. I see YamlList in the debugger only when targets is active.

wujek-srujek avatar May 10 '22 16:05 wujek-srujek

Ah, I think I know what is happening here. The default options end up written to the generated build script as a list literal in Dart, which then gets a type of List<String> from inference. For example:

https://github.com/dart-lang/build/blob/8ebdc5a9d5db8cea9cdf0cb2df80ab25fe8f16db/_test/test/goldens/generated_build_script.dart#L114

In the short term we probably want to explicitly type that as List<dynamic> so that this behavior is less confusing.

In the long term I like a suggestion @jakemac53 made offline - we should stop encoding this data into the Dart code. If we use the version parsed at build time it will be more consistent, and may have better invalidation behavior.

natebosch avatar May 10 '22 16:05 natebosch