language icon indicating copy to clipboard operation
language copied to clipboard

[Feature Request] - Contracts

Open xeinebiu opened this issue 3 years ago • 2 comments

As it happens we write extension functions to check agains some validation, the compiler won't know that the value is validated inside a method as Not Null or something else and still will complain.

As example, on Kotlin there are Contracts. https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.contracts/contract.html

On the code below, the compiler still will complain about Null check.

extension StringNullableExt on String? {
  bool get isNullOrEmpty {
    final instance = this;
    return instance == null || instance.isEmpty;
  }
}
String? somethingMayBeNull = arguments["something"];

if (somethingMayBeNull.isNullOrEmpty) return;
    
String notNull = somethingMayBeNull; // A value of type 'String?' can't be assigned to a variable of type 'String'.

xeinebiu avatar Mar 13 '22 08:03 xeinebiu

The only contract between a caller and the called function is the function type.

Hanging more information onto boolean results (or null/non-null results), so that the caller knows something more if the return values is true, seems potentially useful, but complicated.

On the other hand, since bool is such a small type, I'd often prefer to just return the information directly, encoded as the thing I'd otherwise need to check for anyway.

In this case:

extension MyStringExtension on String {
  String? get emptyAsNull => this.isNotEmpty ? this : null;
}
extension MyStringQExtension on String? {
  String get nullAsEmpty => this ?? "";
}

and then use it as

String? nonEmptyString = arguments["something"]?.emptyAsNull;
if (nonEmptyString == null) return;
// nonEmptyString promoted to String

or

String nonNullString = arguments["something"].nullAsEmpty;
if (nonNullString.isEmpty) return;
// It's a non-empty string.

lrhn avatar Mar 13 '22 10:03 lrhn

Dart really needs to support contracts.

Kotlin already has it. For example, Kotlin's standard library includes the check function:

@kotlin.internal.InlineOnly
public inline fun check(value: Boolean): Unit {
    contract {
        returns() implies value
    }
    check(value) { "Check failed." }
}

where contract is a keyword that allows you to specify the contract of the function. In this case, it specifies that the function returns if the value is true. Thanks to contract, the compiler now knows that the function returns if the value is true, and can use this information to optimize the code.

val foo = someFunction()
check(foo != null) { "foo should not be null" }

// Now we can safely call foo without null check or null assertion operator (!!)
foo.bar()

Similarly, typescript has contracts as well. For example, you can define a check function like this:

export function check(condition: boolean, message: string): asserts condition {
  if (!condition) {
    throw new Error(message)
  }
}

In this case, asserts condition is a contract that tells the compiler that the function throws an error if the condition is false. This allows the compiler to optimize the code and remove unnecessary null checks.

const foo = someFunction()
check(foo !== null, "foo should not be null")

// Now we can safely call foo without null check or null assertion operator (!)
foo.bar()

Dart should support contracts as well. It would make the code more readable, maintainable, and optimized. It would also help prevent bugs and errors by providing compile-time checks for preconditions and postconditions.

bc-lee avatar May 28 '24 02:05 bc-lee