ceylon / ceylon-compiler

DEPRECATED
GNU General Public License v2.0
138 stars 36 forks source link

assert fails for sequence type #2422

Closed ePaul closed 8 years ago

ePaul commented 8 years ago

I'm not sure into which repository this belongs ... it is a runtime issue which doesn't happen in the JavaScript version, only in JVM (with the released 1.2, I didn't try any newer).

// value
interface V {}

// list
class Li(shared V* items) satisfies V {}

// symbol
class S(String name) satisfies V {
    string => name;
}

shared void run() {
    value f = Li(Li(S("#1")));

    value ns = f.items[0];
    value n = if (is Li ns) then ns.items else ns;
    print(n);
    assert (exists n);
    print(className(n));
    assert (is Sequential<Anything> n);
    assert (exists first = n[0]);
    print(first);
    print(className(first));
    assert (is S first);
    assert (is S[] n);
    //assert(is Li[] n);

    print("Okay.");
}

The last assert assert(is S[] n); fails in the JVM, but runs fine when I select "run as JavaScript application" in the IDE. (For comparison, when removing the // in the following line, that will throw in JS.)

I'm quite sure that this assert should not fail (this is a sequence containing one S object, as the other asserts and prints show). Or did I understand this wrong? If so, how can I come to an [S*] or even {S*} from this n?

jvasileff commented 8 years ago

@ePaul the type of items is V[], not its subtype S[]. But you can make Li generic to have the more specific type:

// value
interface V {}

// list
class Li<Element>(shared Element* items)
        satisfies V given Element satisfies V {}

// symbol
class S(String name) satisfies V {
    string => name;
}

shared void run() {
    value f = Li(Li(S("#1")));
    value ns = f.items[0];
    Anything n = if (is Li<out Anything> ns) then ns.items else nothing;
    assert (is S[] n);
    print("Okay.");
}

FYI, rather than className(), you can use type() to see the type with type parameters.

jvasileff commented 8 years ago

Interestingly, if you use

value f = Li(Li(*[S("#1")]));

in your original example, the assertion passes. The spread becomes a no-op and n returns the more specific tuple [S] created in run. But I doubt this is guaranteed behavior.

ePaul commented 8 years ago

Interestingly, if you use

  value f = Li(Li(*[S("#1")]));

in your original example, the assertion passes. The spread becomes a no-op and n returns the more specific tuple [S] created in run . But I doubt this is guaranteed behavior.

Actually, I tried this, but in a different method which doesn't know anything about the type S, so it doesn't help.

My lists are usually mixed lists (which can contain other stuff than just S and Li), just in this case I need one containing just symbols – therefore the type parameter won't really help, as the method constructing those Li entries (a parser or even a user-called function) doesn't know about this.

I guess what I want is to take a V[] and check at runtime if actually all elements are S (and have the compiler recognize this check when successful).

It looks like this does what I want:

    value sn = [*n.narrow<S>()];
    assert( sn == n);

Is there some way to do this in one step, i.e. without having to compare after the filtering?

I just hacked this together:

[E&T*] assertNarrow<T, E=Anything>({E*} iterable) =>
        iterable.collect((E e) {
        if (is T e) {
            return e;
        } else {
            throw AssertionError("`` e else "null" `` is not of type `` `T` ``.");
        }
    });

    S[] snn = assertNarrow<S>(n); // passes
    Li[] ln = assertNarrow<Li>(n); // fails

It works as expected.

Is something like this already there?

jvasileff commented 8 years ago

As far as I know that's right. Type parameters should be considered immutable, so you need to rebuild the container to change them.

I don't think anything exists for this, but I agree, it should! Maybe open an issue in ceylon.language to have something added to Iterable?

I've run into this exact scenario once or twice, and had to really stop myself from being lazy and skipping the assertion with something like items.narrow<S>().sequence();.

gavinking commented 8 years ago

Is there some way to do this in one step, i.e. without having to compare after the filtering?

Just check the size of the two sequences, you don't need to compare all their elements.

Type parameters should be considered immutable, so you need to rebuild the container to change them.

Right.

gavinking commented 8 years ago

@ePaul Can we close this issue now?

ePaul commented 8 years ago

@gavinking yes, this was just a misunderstanding (from me). Thanks for the explanations.

gavinking commented 8 years ago

Cheers, thanks.