General Pattern

We need a way to expres that a class implementing an interface should implement all the methods required by the interface.

interface I{
	public void foo();
}

class A implements I{
	
}

There should be an error on A: The type A must implement the inherited abstract method I.foo()

This pattern surfaces with required attributes in entities.

model
entity X{
	i : Int
	j : String
}

data
X x{
	i = 3
}

This should give an error on x: x.j should be set.

Solution (Tasks)

For classes implementing interface the solution will be along the lines of.

  1. match on the Class(name, _, implementsInterfaces, _, methods)
  2. resolve interface to inteface uri by looking at the Use(Def(…)) => / ClassOrInterface:"I"#0 /
  3. get all method uris inside interface uri => [ / ClassOrInterface:"I"#0 / Method:"foo"#0 / , ... ]
  4. get all method uris inside self => [ ... ]
  5. use stratego to find missing, and if there are give error on name

However this only works if the thing matched on defines stuff in the index.

The entity instantiation does not define the attributes(fields) inside the entity. So in that case step 4 does not apply and one compares the methods in ‘Class(…)’ directly with the method uris.

Solution (TS and NaBL)

Submitted by Daco Harkes on 13 March 2014 at 13:39

On 13 March 2014 at 14:19 Guido Wachsmuth commented:

You can do some of this with existing tasks, which we typically use for content completion. These tasks will give you all definitions in a scope, for example all methods of an interface or all methods of the class. There is also a task which turns the results of another task into a list as a single result, but this might not even be needed. The comparison is the issue here. Maybe you can come up with a new task for this. This task should be a combinator and should take two parameters s1 and s2. It should result in s1 \ s2. When this task has no results, everything is fine in your example.


On 13 March 2014 at 14:24 Gabriël Konat commented:

One more problem is that properties of definitions also need to be taken into account. In Java we need to compare the signature of methods, not their names.


On 13 March 2014 at 14:28 Guido Wachsmuth commented:

This is probably related to the solution for disambiguation/filtering then. But for Daco’s use case, a difference task should be sufficient. BTW, there is already a Diff task, what does exactly what I described.


On 19 March 2014 at 17:31 Gabriël Konat commented:

A possible syntax:

t@ClassDec(m*, c, SuperDec(sc), _) :-
where not m* contains Abstract()
  and foreach Method m
    with modifiers Abstract()
    in definition of sc
  where m has parameter-types p-ty*
    and surrounding Type contains Method m
      with parameter-types p-ty*
      without modifiers Abstract()
  else error "Non-abstract class must implement all inherited abstract methods" on t

On 31 March 2015 at 13:58 Daco Harkes commented:

The “contains all of something” has been generalized to a stratego implementation.

Interface:

ContainsAll(
  namespace of which all should be present,
  filter on properties of elements in the namespace,
  function that succeeds for element which should contain all items,
  function that gets the definition for this element,
  function that gets all uses in the element,
  function that gets the element name (for error reporting)
)

Use:

rules // check if entities have all the required attributes and roles assigned
	
	contains-all-errors = ![
		ContainsAll(
			NablNsAttribute(),
			[
				(NablProp_derivation-type(), Normal()),
				(NablProp_multiplicity(), One())
			],
			"ei-a1",
			"ei-a2",
			"ei-a3",
			"ei-a4"
		),
		ContainsAll(
			NablNsRole(),
			[],
			"ei-r1",
			"ei-r2",
			"ei-r3",
			"ei-r4"
		)
	]
	
	eval(|f) = where("ei-a1":=f); is-entityinstance
	eval(|f) = where("ei-a2":=f); entityinstance-get-type
	eval(|f) = where("ei-a3":=f); entityinstance-get-attrvalues;map(attributevalue-get-attr)
	eval(|f) = where("ei-a4":=f); entityinstance-get-name
	
	eval(|f) = where("ei-r1":=f); is-entityinstance
	eval(|f) = where("ei-r2":=f); entityinstance-get-type
	eval(|f) = where("ei-r3":=f); entityinstance-get-rolevalues;map(rolevalue-get-role)
	eval(|f) = where("ei-r4":=f); entityinstance-get-name

Implementation:

rules // api for checking something contains all of def

	// interface implemented by language
	contains-all-errors = fail
	eval(|f)            = fail

	nabl-constraint(|ctx): a -> <fail>
		with
			contains-all-errors;map(try(nabl-constraint(|ctx, a)))

	nabl-constraint(|ctx, a): f -> <id>
		where
			ContainsAll(
				def-child-ns, 
				filters,
				f-on,
				f-def-of,
				f-ast-children,
				f-msg-on
			) := f
		where
			<eval(|f-on)>a
		with
			a-ty                    := <eval(|f-def-of)>a;
			a-ty-def 	              := <_nabl-create-collectdefs(|ctx)> a-ty;
			[a-ty-def-children]     := <_nabl-resolve-all-tasks(|ctx, def-child-ns, [])> [a-ty-def];
			a-children              := <eval(|f-ast-children)>a;
			a-children-defs         := <map(_nabl-create-collectdefs(|ctx));_task-create-combine(|ctx)>a-children;
			diff                    := <_task-create-diff(|ctx)> (a-ty-def-children, a-children-defs);
			diff-filtered           := <filter-propconstraints(|ctx,filters)>diff;
			a-msg-on                := <eval(|f-msg-on)>a;
			msg                     := ["Provide all required ", <_pp-ns>def-child-ns, "s: ", diff-filtered];
			<_task-create-error-on-success(|ctx, diff-filtered, msg)> a-msg-on

	filter-propconstraints(|ctx, filters) = where(!(filters, []);equal);id
	filter-propconstraints(|ctx, filters): def-list -> def-list-filtered-rec
		where
			!(filters, []);not(equal)
		with
			[filter|filters-tail] := filters;
			(nablprop, propvalue) := filter;
			def-list-filtered     := <_nabl-create-propconstraint(|ctx, nablprop, def-list)> propvalue;
			def-list-filtered-rec := <filter-propconstraints(|ctx, filters-tail)>def-list-filtered

Log in to post comments