Skip to content

Impact of adding private membersΒ #3826

@eernstg

Description

@eernstg

Consider the following program consisting of two libraries:

// --- Library 'n045lib.dart' (version 3).

abstract class A {
  int get _private;
}

void foo(A a) => print(a._private);

// --- Library 'n045.dart'

import 'n045lib.dart';

class B extends A {}

void main() {
  foo(B());
}

This program is accepted by the analyzer as well as the common front end, and it throws at run time because the given instance of B does not have an implementation of _private.

I have argued (e.g., here) that this situation should be subject to a warning (or an error). However, the counterargument has been raised (e.g., here) that it is more important to avoid that the addition of a private declaration causes code in other libraries to fail.

// --- Library 'n045lib.dart', version 1.

abstract class A {}

void foo(A a) {}

// --- Library 'n045lib.dart', version 2.

abstract class A {
  int get _private;
}

void foo(A a) {}

// --- Library 'n045lib.dart', version 3 again.

abstract class A {
  int get _private;
}

void foo(A a) => print(a._private);

Surely, B should not be an error with version 1. According to the "addition of private members is non-breaking" principle, B should not be an error with version 2, either. Surely, then, version 3 cannot make B an error.

So we accept that B does not have an implementation of _private because it would be worse to report an error for B based on such updates of the library that declares A.

However, if we maintain that the addition of a private member should not cause an error to occur in a different library then we should also accept the following program with no compile-time errors:

// --- Library 'n046lib.dart', version 1.

class A {}

abstract class B extends A {
  int _private([int _]);
}

void foo(B b) => print(b._private());

// --- Library 'n046lib.dart', version 2.

class A {
  int _private() => 0;
}

abstract class B extends A {
  int _private([int _]);
}

void foo(B b) => print(b._private());

// --- Library 'n046.dart'.

import 'n046lib.dart';

class C extends B {}
 
void main() {
  foo(C());
}

The analyzer again accepts both versions of the code, but the common front end reports an error for version 2, where we have added the private declaration A._private:

n046.dart:3:7: Error: The implementation of '_private' in the non-abstract class 'C' does not conform to its interface.
class C extends B {}
      ^
n046lib.dart:2:7: Context: The method 'A._private' has fewer positional arguments than those of overridden method 'B._private'.
  int _private() => 0;
      ^
n046lib.dart:6:7: Context: This is the overridden method ('_private').
  int _private([int _]);
      ^

Again, we'd incur a run-time failure because the instance of C does not have an implementation of _private (and the one which is declared in A can not be used because it does not have the required signature). We aren't able to run it, though, because of the error.

We should at least address the discrepancy such that all tools will agree on the existence/non-existence of a compile-time error.

  • Option 1: Make CFE stop reporting this error, it is always OK for a class declared in a library L to have no implementation for some private members in a different library L2.
  • Option 2: Drop the principle about "adding a private member doesn't break code in other libraries" and report the error in all tools (this would affect both 'n045.dart' and 'n046.dart').

@dart-lang/language-team, WDYT?

Metadata

Metadata

Assignees

No one assigned

    Labels

    questionFurther information is requested

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions