Skip to content

Comparison with default object in optional parameter fails in JS release build #61767

@rebaz94

Description

@rebaz94

I encountered a strange issue where code that works fine in dev mode (and in native builds) fails in the JS release build.

The issue seems to happen when using a function that takes an optional parameter defaulting to an object (freezed), and comparing it like disabled == freezed.
In JS release mode, that comparison always fails, even when the value should match.

Example 1

This example uses a simple class without code generation — it works correctly in all modes.

const freezed = Object();

enum BlockTag { unset }

abstract class FnData1 {
  FnData1 withUpdateOf({
    BlockTag? blockTag = BlockTag.unset,
    String? description,
    Object? disabled,
    List<String>? content,
  });

  bool? get isDisabled;
}

class Example implements FnData1 {
  final int id;
  final bool? disabled;

  Example({required this.id, this.disabled});

  // Simulates what Freezed would generate
  Example copyWith({required int id, required bool? disabled}) {
    return Example(id: id, disabled: disabled);
  }

  @override
  FnData1 withUpdateOf({
    BlockTag? blockTag = BlockTag.unset,
    String? description,
    Object? disabled = freezed,
    List<String>? content,
  }) {
    return copyWith(
      id: id,
      disabled: disabled == freezed
          ? this.disabled
          : disabled == true
              ? true
              : null,
    );
  }

  @override
  bool? get isDisabled => disabled;

  @override
  String toString() => 'Example(id: $id, disabled: $disabled)';
}

void main() {
  final data = Example(id: 1, disabled: true);
  final updated1 = data.withUpdateOf();
  print('update1: disabled: ${updated1.isDisabled}, correct: ${updated1.isDisabled == true}'); // should be true

  final updated2 = data.withUpdateOf(disabled: false); // should set disabled = null
  print('update2: disabled: ${updated2.isDisabled}, correct: ${updated2.isDisabled == null}'); // should be null

  print('-------');

  final FnData1 data2 = Example(id: 1, disabled: true);
  final updated3 = data2.withUpdateOf();
  print('update3: disabled: ${updated3.isDisabled}, correct: ${updated3.isDisabled == true}'); // should be true

  final updated4 = data2.withUpdateOf(disabled: false); // should set disabled = null
  print('update4: disabled: ${updated4.isDisabled}, correct: ${updated4.isDisabled == null}'); // should be null
}

Example 2

This example uses a function generated by Freezed that implements FnData.
In this case, the comparison fails only in JS release mode.

part 'main.freezed.dart';

@freezed
class FnExample with _$FnExample implements FnData {
  FnExample._();

  factory FnExample({
    required String id,
    BlockTag? blockTag,
    String? description,
    bool? disabled,
  }) = _FnExample;

  factory FnExample.newInstance({required String id, bool? disabled}) {
    return FnExample(id: id, disabled: disabled);
  }

  @override
  FnData withUpdateOf({
    String? id,
    BlockTag? blockTag = BlockTag.unset,
    String? description,
    Object? disabled = freezed,
  }) {
    return copyWith(
      id: id ?? this.id,
      parentId: parentId ?? this.parentId,
      blockTag: blockTag != BlockTag.unset ? blockTag : this.blockTag,
      description: description != null ? (description.isEmpty ? null : description.trim()) : this.description,
      disabled: disabled == freezed
          ? this.disabled
          : disabled == true
          ? true
          : null,
    );
  }
}



void main() {
  final FnData data3 = FnExample.newInstance(id: '1', disabled: true);
  final updated5 = data3.withUpdateOf();
  print('update5: disabled: ${updated5.disabled}, correct: ${updated5.disabled == true}'); // should be true, but it's false in JS release

  final updated6 = data3.withUpdateOf(disabled: false); // should set disabled = null
  print('update6: disabled: ${updated6.disabled}, correct: ${updated6.disabled == null}'); // should be null (works correctly)
}

Expected behavior

  • disabled == freezed should evaluate consistently across all build modes.
  • The first call (withUpdateOf()) should preserve the same disabled state as before.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-web-jsIssues related to JavaScript support for Dart Web, including DDC, dart2js, and JS interop.web-dart2js

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions