Skip to content
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

Constant parameters #1684

Open
bwilkerson opened this issue Jun 16, 2021 · 8 comments
Open

Constant parameters #1684

bwilkerson opened this issue Jun 16, 2021 · 8 comments
Labels
enhanced-const Requests or proposals about enhanced constant expressions feature Proposed language feature that solves one or more problems

Comments

@bwilkerson
Copy link
Member

There is a request to add an annotation for parameters whose semantics ensures that the argument corresponding to the annotated parameter is a constant expression.

It occurred to me that this might be an interesting language feature. The proposal would be to allow the modifier const on parameter declarations and for the semantics to match those requested for the annotation. For example,

void f(const List<int> x) {}

void g(int a) {
  f(const [1, 2, 3]); // OK because it's explicitly const.
  f([1, 2, 3]); // OK because it's implicitly const.
  f([a]); // ERROR because `a` isn't a valid const expression
}

It might additionally provide some possibilities for compiler optimizations if it's known that the value of the parameter can't be modified.

I suspect that we'd want to introduce the annotation first and see how/whether it's used in practice, but I thought I'd mention it in case you'd want to track the work on the annotation.

@bwilkerson bwilkerson added the feature Proposed language feature that solves one or more problems label Jun 16, 2021
@lrhn
Copy link
Member

lrhn commented Jun 18, 2021

It's probably possible, but not something I think is going to be easy, or necessarily worth it.

As a language feature, it would need to be linked into the type system. A static requirement when calling a function comes from the static type of the function. Effectively, some arguments would be annotated const, and function calls would need to use a const expression for the corresponding arguments.

That also means that int Function(int) and int Function(const int) are different types, most likely the former being a subtype of the latter (since nothing prevents you from passing constant values to non-constant parameters).

If it touches the type system, that makes it a feature with a major impact.
It's a "non breaking" feature in that there are no existing const-parameters declared. That means that we can introduce the feature without needing migration or "legacy interpretations", like the ones we needed for null safety.

On the other hand, it's breaking to make any existing parameter be const , so if anyone intends to use the feature, they'll have to prepare for breaking changes or migrations. If we just enforce the const requirement for post-feature-release-language-version code, and only give warnings for older code, then it might be possible to migrate code to use the feature without breaking other code, as long as you migrate the declarations first.
That might (or might not) give a better migration experience than just enforcing the feature on all code. It also means you can't trust the feature until all code is migrated to the newer language version (like for null safety).

It's only for parameters. I guess that's reasonable, we can already make variables const. (Should we allow it for instance variables too, so an instance variable must be initialized with a constant expression? That doesn't seem useful.)
I also can't find a good use for making a return type const because it would only affect the return statement expression, the places where you call the function are already non-constant.

Can covariant allow you to override a non-const parameter with a const parameter? If not, why not?
(Because it's unsafe, but covariant is known to be unsafe already. So perhaps because we can't detect breaches of the safety, because there is no way to distinguish a const expression value from a non-const expression value after it has been created).

Can you abstract over const in generics? typedef F<T> = int Function(T); ... F<const int> f = ...;? If not, why not? (Because it's not a type, but then you can't abstract over all unary functions any more using typedef <R,P> = R Function(P);).

All in all, I don't think the scale of the feature matches the benefits. That said, I don't really understand why you'd require const arguments to begin with (a Dart constant value isn't significantly different from a non-const instance of the same type, so unless it's a List or Map, requiring you to use const values is very much a domain requirement, not something that makes sense in general software development. A domain specific language feature risks having a very low usage outside of one or two specific domains, and then I'm not sure the development effort and the contextual complexity that it adds to the language are worth the opportunity cost.

@bwilkerson
Copy link
Member Author

Those are all good questions, and I tend to agree with your assessment. It would indeed need to be part of the type system.

I don't really understand why you'd require const arguments to begin with ...

This might be the wrong way to approach the problem, but the original goal was to be able to define APIs that would otherwise be vulnerable to attack in such a way that user data couldn't be provided to them. For example, an API that uses arguments to compose a database query could be written such that the arguments are required to be compile-time constants, thus preventing the possibility that some user-entered value could accidentally be passed in.

@lrhn
Copy link
Member

lrhn commented Jun 18, 2021

You can do awful things with constants, interface implementation and either expandos or noSuchMethod if you really want to.

class SupposedlySafe implements Safe {
  const SupposedlySafe();
  static _state = Expando<String>();
  String get someProperty => _state[this];
  void set someProperty(String value) { _state[this] = value; }
}

That'll let you create a Safe object which is constant, but where the properties can change anyway.

You can't trust malicious code, or protect yourself from it, if it runs in the same isolate. The type system is not a security system.

@bwilkerson
Copy link
Member Author

True, but I probably wasn't clear enough. They aren't executing user-written code. This is helping to protect against things like user-entered text from a text widget accidentally being passes to a trusted method. There's no delusion that this will prevent every kind of bug, just that it will reduce the probability of one kind of bug.

@cedvdb
Copy link

cedvdb commented Jul 1, 2021

Would this solve the issue of not being able to create a constant like this:

  const spacings = Spacings(xs: 2, s: 4, m: 8);
  const padding = EdgeInsets.all(spacings.m);

When writing a package there is no way of requiring a set of spacings and create const paddings from those spacing ?

@Levi-Lesches
Copy link

I may be wrong, but that feels like a separate problem:

const Spacings spacings = Spacings(xs: 2, s: 4, m: 8);
const int number = spacings.m;  // error: invalid constant value
const EdgeInsets padding = EdgeInsets.all(number);

The issue isn't that Spacings doesn't know its parameters are constant, the issue is that Spacings.m is not known to be constant. The m in the constructor doesn't necessarily match up with the m getter. Consider the following Spacings definition instead:

class Spacings2 {
  final int _m;
   
  const Spacings({required int xs, required int s, required int m}) : 
    this._m = m;

  int get m => myLogic(_m);  // not constant
}

This would require the notion of a field-only getter (so we can assign a value and be sure it is never touched), which doesn't exist yet in Dart.

@Levi-Lesches
Copy link

Can you abstract over const in generics? typedef F<T> = int Function(T); ... F<const int> f = ...;? If not, why not? (Because it's not a type, but then you can't abstract over all unary functions any more using typedef <R,P> = R Function(P);).

Chiming in with this code snippet from #1774 (multiple generics). This feature requires both built-in tuples (#207 or #68 are similar) and constant parameters. I added some comments for context.

/// `T[]` denotes a list or tuple of type arguments. 
///
/// I'll use `<int, String, bool>` in my examples
class Tuple<T[]> {
  /// [T] is a tuple or list of types. In this case: `<int, String, bool>`
  final T items;

  /// Using the example from earlier, this can be `Tuple([1, "Hello", true])`.
  ///
  /// Maybe the spread operator (`...`) can be used to unpack these, so you can simply
  /// do: `Tuple(1, "Hello", true)`. See https://github.com/dart-lang/language/issues/1014.
  Tuple(T this.items);

  /// This returns `<dynamic>[1, "Hello", true]`.
  List<dynamic> toList() => items;

  /// `getStaticItem(0)` returns the first element, statically known to be an int.
  T[comptimeIndex] getStaticItem(const int comptimeIndex) => item[comptimeIndex];

  /// `getDynamicItem(index)` returns the value at the corresponding index. 
  ///
  /// Since the index is only known at runtime, the type is unknown ahead of time, and 
  /// this is subject to an [IndexError]. 
  dynamic getDynamicItem(int runtimeIndex) => item[runtimeIndex];

  /// Returns a [Tuple] with one element replaced, but with the same type. 
  /// 
  /// `tuple.withItem(1, "World")` returns `Tuple([1, "World", true])`.
  Tuple<T> withItem(const int i, T[i] item) => Tuple(...item.sublist(0, i), item, ...item.sublist(i+1));
}

@dcharkes
Copy link
Contributor

For future reference if we ever revisit this.

Propagate consts can lead to a space explosion as detailed in (#2776 (comment)). Also, when doing propagation, we quickly get into the question whether control flow should be supported and whether string concatination or any other const-able operation should be supported as discussed above. These can lead to more space explosion.

(For now, the use cases in dart-lang/native#153 don't seem to need it. And dart-lang/sdk#29381 is going for an annotation instead of a language feature for now.)

copybara-service bot pushed a commit to dart-lang/sdk that referenced this issue Apr 4, 2024
constant.

As const-only parameters are not planned, see
dart-lang/language#1684, an annotation with analyzer support could help a bit at least, see
#29381.

The motivation is to enforce const arguments for methods annotated with
`@ResourceIdentifier`, to be able to record the argument values at build time, see https://dart-review.googlesource.com/c/sdk/+/329961.

Tested: pkg/analyzer/test/src/diagnostics/const_argument_test.dart
Change-Id: I2b8d2dce0c899fc0caa4985d892a5d031c747521
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/357701
Reviewed-by: Phil Quitslund <[email protected]>
Reviewed-by: Lasse Nielsen <[email protected]>
Reviewed-by: Brian Wilkerson <[email protected]>
Commit-Queue: Moritz Sümmermann <[email protected]>
Reviewed-by: Marya Belanger <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhanced-const Requests or proposals about enhanced constant expressions feature Proposed language feature that solves one or more problems
Projects
None yet
Development

No branches or pull requests

6 participants