]> git.ipfire.org Git - thirdparty/vala.git/commitdiff
Add support for "with" statement 35f72f3ecdf0d61d0e67cd4f72c29fee2c3c9a55
authorNick Schrader <nick.schrader@mailbox.org>
Fri, 27 Mar 2020 16:12:09 +0000 (13:12 -0300)
committerRico Tzschichholz <ricotz@ubuntu.com>
Tue, 2 Jun 2020 11:02:42 +0000 (13:02 +0200)
Creates data type scoped blocks which allow implicit member access to
the given expression or declaration statement.

with (expr) {
    ...;
}

Within the with-block the expression's members can be directly accessed
without the member access operator.
Members may hide local, class and instance varibales with the same name.
Instance variables are still accessible through this.
A local variable can be directly declared in the with statement header.
Hidden local and class variables are currently not directly accessible
(using this for class members generates the expected warning).

Fixes https://gitlab.gnome.org/GNOME/vala/issues/327

41 files changed:
tests/Makefile.am
tests/nullability/with-non-null.test [new file with mode: 0644]
tests/objects/with-expression.vala [new file with mode: 0644]
tests/objects/with-instance.vala [new file with mode: 0644]
tests/objects/with-nested.vala [new file with mode: 0644]
tests/parser/with-embedded.vala [new file with mode: 0644]
tests/parser/with-empty.vala [new file with mode: 0644]
tests/parser/with-invalid-declaration.test [new file with mode: 0644]
tests/parser/with-invalid-expression.test [new file with mode: 0644]
tests/parser/with-no-block.test [new file with mode: 0644]
tests/parser/with-no-expression.test [new file with mode: 0644]
tests/semantic/with-array.test [new file with mode: 0644]
tests/semantic/with-buildin.vala [new file with mode: 0644]
tests/semantic/with-class.test [new file with mode: 0644]
tests/semantic/with-compact.vala [new file with mode: 0644]
tests/semantic/with-declaration-cast-type.vala [new file with mode: 0644]
tests/semantic/with-declaration-wrong-type.test [new file with mode: 0644]
tests/semantic/with-declaration.vala [new file with mode: 0644]
tests/semantic/with-dereferenced-pointer.vala [new file with mode: 0644]
tests/semantic/with-enum-member.vala [new file with mode: 0644]
tests/semantic/with-enum.test [new file with mode: 0644]
tests/semantic/with-error-member.test [new file with mode: 0644]
tests/semantic/with-error.test [new file with mode: 0644]
tests/semantic/with-namespace.test [new file with mode: 0644]
tests/semantic/with-no-declaration.test [new file with mode: 0644]
tests/semantic/with-no-such-member.test [new file with mode: 0644]
tests/semantic/with-null.vala [new file with mode: 0644]
tests/semantic/with-outside-declaration.test [new file with mode: 0644]
tests/semantic/with-pointer.test [new file with mode: 0644]
tests/semantic/with-string.vala [new file with mode: 0644]
tests/semantic/with-value.vala [new file with mode: 0644]
vala/Makefile.am
vala/valacodevisitor.vala
vala/valacodewriter.vala
vala/valaflowanalyzer.vala
vala/valamemberaccess.vala
vala/valaparser.vala
vala/valascanner.vala
vala/valasymbolresolver.vala
vala/valatokentype.vala
vala/valawithstatement.vala [new file with mode: 0644]

index 263e8975e4c509a706ab9ca9addcf2adbc034c1b..24e8964faabb940c0c76d2def3b0b007317b1fc4 100644 (file)
@@ -505,6 +505,9 @@ TESTS = \
        objects/bug795225-3.test \
        objects/bug795225-4.test \
        objects/bug795521.vala \
+       objects/with-expression.vala \
+       objects/with-instance.vala \
+       objects/with-nested.vala \
        errors/catch-error-code.vala \
        errors/catch-in-finally.vala \
        errors/default-gtype.vala \
@@ -734,6 +737,12 @@ TESTS = \
        parser/using-ambiguous-reference.test \
        parser/using-directive.vala \
        parser/using-invalid-namespace.test \
+       parser/with-embedded.vala \
+       parser/with-empty.vala \
+       parser/with-invalid-declaration.test \
+       parser/with-invalid-expression.test \
+       parser/with-no-block.test \
+       parser/with-no-expression.test \
        parser/yield-method.test \
        parser/yield-return.test \
        parser/yield-return.vala \
@@ -928,6 +937,26 @@ TESTS = \
        semantic/unary-unsupported-increment.test \
        semantic/unary-unsupported-minus.test \
        semantic/unary-unsupported-negation.test \
+       semantic/with-array.test \
+       semantic/with-buildin.vala \
+       semantic/with-class.test \
+       semantic/with-compact.vala \
+       semantic/with-declaration-cast-type.vala \
+       semantic/with-declaration-wrong-type.test \
+       semantic/with-declaration.vala \
+       semantic/with-dereferenced-pointer.vala \
+       semantic/with-enum.test \
+       semantic/with-enum-member.vala \
+       semantic/with-error.test \
+       semantic/with-error-member.test \
+       semantic/with-pointer.test \
+       semantic/with-namespace.test \
+       semantic/with-no-declaration.test \
+       semantic/with-no-such-member.test \
+       semantic/with-null.vala \
+       semantic/with-string.vala \
+       semantic/with-outside-declaration.test \
+       semantic/with-value.vala \
        semantic/yield-call-requires-async-context.test \
        semantic/yield-call-requires-async-method.test \
        semantic/yield-creation-requires-async-context.test \
@@ -941,6 +970,7 @@ NON_NULL_TESTS = \
        nullability/member-access-nullable-instance.test \
        nullability/method-parameter-invalid-convert.test \
        nullability/method-return-invalid-convert.test \
+       nullability/with-non-null.test \
        $(NULL)
 
 LINUX_TESTS = \
diff --git a/tests/nullability/with-non-null.test b/tests/nullability/with-non-null.test
new file mode 100644 (file)
index 0000000..ed8fb40
--- /dev/null
@@ -0,0 +1,12 @@
+Invalid Code
+
+class Foo {
+    public int i;
+}
+
+void main () {
+    Foo? f = new Foo ();
+    with (f) {
+        i = 7;
+    }
+}
diff --git a/tests/objects/with-expression.vala b/tests/objects/with-expression.vala
new file mode 100644 (file)
index 0000000..8e2821d
--- /dev/null
@@ -0,0 +1,38 @@
+class Foo {
+    public static int factory_called = 0;
+    public static Foo factory () {
+        factory_called++;
+        return new Foo ();
+    }
+
+    public static int method_called = 0;
+    public void method () {
+        method_called++;
+    }
+}
+
+void test () {
+    var foo = new Foo ();
+    with (foo)
+        method ();
+
+    with (new Foo ())
+        method ();
+
+    with (Foo.factory ()) {
+        method ();
+        method ();
+    }
+
+    Foo[] arr = {foo, foo};
+    with (arr[0]) {
+        method ();
+    }
+
+    assert (Foo.method_called == 5);
+    assert (Foo.factory_called == 1);
+}
+
+void main () {
+    test ();
+}
diff --git a/tests/objects/with-instance.vala b/tests/objects/with-instance.vala
new file mode 100644 (file)
index 0000000..715833e
--- /dev/null
@@ -0,0 +1,73 @@
+class Foo {
+    public int field;
+    public string prop { get; set; }
+
+    public bool method_called = false;
+    public void method () {
+        method_called = true;
+    }
+}
+
+class Bar : Foo { }
+
+class TestFoo {
+    public static int class_field;
+    public int instance_field;
+
+    public void test () {
+        var foo = new Foo ();
+        var local_field = 0;
+
+        with (foo) {
+            field = 10;
+            prop = "prop";
+            method ();
+
+            local_field = 20;
+            class_field = 30;
+            instance_field = 40;
+        }
+
+        assert (foo.field == 10);
+        assert (foo.prop == "prop");
+        assert (foo.method_called);
+
+        assert (local_field == 20);
+        assert (class_field == 30);
+        assert (instance_field == 40);
+    }
+}
+
+// Copy and paste TestFoo, change Foo to Bar
+class TestBar {
+    public static int class_field;
+    public int instance_field;
+
+    public void test () {
+        var foo = new Bar ();
+        var local_field = 0;
+
+        with (foo) {
+            field = 10;
+            prop = "prop";
+            method ();
+
+            local_field = 20;
+            class_field = 30;
+            instance_field = 40;
+        }
+
+        assert (foo.field == 10);
+        assert (foo.prop == "prop");
+        assert (foo.method_called);
+
+        assert (local_field == 20);
+        assert (class_field == 30);
+        assert (instance_field == 40);
+    }
+}
+
+void main () {
+    new TestFoo ().test ();
+    new TestBar ().test ();
+}
diff --git a/tests/objects/with-nested.vala b/tests/objects/with-nested.vala
new file mode 100644 (file)
index 0000000..ac35665
--- /dev/null
@@ -0,0 +1,34 @@
+class Foo {
+    public int field;
+}
+
+class Bar {
+    public int field;
+}
+
+class Test {
+    public int field;
+
+    void nested () {
+        var foo = new Foo ();
+        var bar = new Bar ();
+
+        with (var f = foo) {
+            field = 100;
+            with (bar) {
+                field = 200;
+                f.field = 300;
+                this.field = 400;
+            }
+        }
+
+        assert (foo.field == 300);
+        assert (bar.field == 200);
+        assert (this.field == 400);
+    }
+
+    static int main () {
+        new Test ().nested ();
+        return 0;
+    }
+}
diff --git a/tests/parser/with-embedded.vala b/tests/parser/with-embedded.vala
new file mode 100644 (file)
index 0000000..c668e5a
--- /dev/null
@@ -0,0 +1,4 @@
+void main () {
+    with (10)
+        assert (to_string () == "10");
+}
diff --git a/tests/parser/with-empty.vala b/tests/parser/with-empty.vala
new file mode 100644 (file)
index 0000000..1a870b6
--- /dev/null
@@ -0,0 +1,3 @@
+void main () {
+    with (10);
+}
diff --git a/tests/parser/with-invalid-declaration.test b/tests/parser/with-invalid-declaration.test
new file mode 100644 (file)
index 0000000..ae1e775
--- /dev/null
@@ -0,0 +1,6 @@
+Invalid Code
+
+void main () {
+    with (var f foo) {
+    }
+}
diff --git a/tests/parser/with-invalid-expression.test b/tests/parser/with-invalid-expression.test
new file mode 100644 (file)
index 0000000..50fbe77
--- /dev/null
@@ -0,0 +1,6 @@
+Invalid Code
+
+void main () {
+    with (f foo) {
+    }
+}
diff --git a/tests/parser/with-no-block.test b/tests/parser/with-no-block.test
new file mode 100644 (file)
index 0000000..237bd89
--- /dev/null
@@ -0,0 +1,5 @@
+Invalid Code
+
+void main () {
+    with (10)
+}
\ No newline at end of file
diff --git a/tests/parser/with-no-expression.test b/tests/parser/with-no-expression.test
new file mode 100644 (file)
index 0000000..0e01d28
--- /dev/null
@@ -0,0 +1,6 @@
+Invalid Code
+
+void main () {
+    with () {
+    }
+}
diff --git a/tests/semantic/with-array.test b/tests/semantic/with-array.test
new file mode 100644 (file)
index 0000000..bf0b4d1
--- /dev/null
@@ -0,0 +1,7 @@
+Invalid Code
+
+void main () {
+    int[] arr = { 1, 2, 3 };
+    with (arr) {
+    }
+}
diff --git a/tests/semantic/with-buildin.vala b/tests/semantic/with-buildin.vala
new file mode 100644 (file)
index 0000000..999554a
--- /dev/null
@@ -0,0 +1,5 @@
+void main () {
+    with (stdout) {
+        assert (!eof ());
+    }
+}
diff --git a/tests/semantic/with-class.test b/tests/semantic/with-class.test
new file mode 100644 (file)
index 0000000..2bea6e5
--- /dev/null
@@ -0,0 +1,9 @@
+Invalid Code
+
+class Foo {
+}
+
+void main () {
+    with (Foo) {
+    }
+}
diff --git a/tests/semantic/with-compact.vala b/tests/semantic/with-compact.vala
new file mode 100644 (file)
index 0000000..b2530c9
--- /dev/null
@@ -0,0 +1,13 @@
+[Compact]
+class Foo {
+    public int i;
+}
+
+void main () {
+    var foo = new Foo ();
+    with (foo) {
+        i = 13;
+    }
+
+    assert (foo.i == 13);
+}
diff --git a/tests/semantic/with-declaration-cast-type.vala b/tests/semantic/with-declaration-cast-type.vala
new file mode 100644 (file)
index 0000000..c855d1c
--- /dev/null
@@ -0,0 +1,10 @@
+class Foo {
+}
+
+class Bar : Foo {
+}
+
+void main () {
+    with (Foo f = new Bar ()) {
+    }
+}
diff --git a/tests/semantic/with-declaration-wrong-type.test b/tests/semantic/with-declaration-wrong-type.test
new file mode 100644 (file)
index 0000000..79ae8f8
--- /dev/null
@@ -0,0 +1,12 @@
+Invalid Code
+
+class Foo {
+}
+
+class Bar {
+}
+
+void main () {
+    with (Bar f = new Foo ()) {
+    }
+}
diff --git a/tests/semantic/with-declaration.vala b/tests/semantic/with-declaration.vala
new file mode 100644 (file)
index 0000000..78763c3
--- /dev/null
@@ -0,0 +1,35 @@
+class Foo {
+    public int i;
+    public int j;
+}
+
+void main () {
+    var foo = new Foo ();
+    with (foo) {
+        i = 10;
+    }
+
+    assert (foo.i == 10);
+    with (var f = foo) {
+        i = 100;
+        f.j = 200;
+    }
+
+    assert (foo.i == 100);
+    assert (foo.j == 200);
+    with (Foo f = foo) {
+        i = 1000;
+        f.j = 2000;
+    }
+
+    assert (foo.i == 1000);
+    assert (foo.j == 2000);
+    Foo f;
+    with (f = foo) {
+        i = 10000;
+        f.j = 20000;
+    }
+
+    assert (f.i == 10000);
+    assert (foo.j == 20000);
+}
diff --git a/tests/semantic/with-dereferenced-pointer.vala b/tests/semantic/with-dereferenced-pointer.vala
new file mode 100644 (file)
index 0000000..42fc20f
--- /dev/null
@@ -0,0 +1,14 @@
+class Foo {
+    public int i;
+}
+
+void main () {
+    var f = new Foo ();
+    var p = &f;
+
+    with (*p) {
+        i = 13;
+    }
+
+    assert (f.i == 13);
+}
diff --git a/tests/semantic/with-enum-member.vala b/tests/semantic/with-enum-member.vala
new file mode 100644 (file)
index 0000000..3b5517d
--- /dev/null
@@ -0,0 +1,8 @@
+enum FooEnum {
+    FIRST
+}
+
+void main () {
+    with (FooEnum.FIRST) {
+    }
+}
diff --git a/tests/semantic/with-enum.test b/tests/semantic/with-enum.test
new file mode 100644 (file)
index 0000000..3a473a2
--- /dev/null
@@ -0,0 +1,10 @@
+Invalid Code
+
+enum FooEnum {
+    FIRST
+}
+
+void main () {
+    with (FooEnum) {
+    }
+}
diff --git a/tests/semantic/with-error-member.test b/tests/semantic/with-error-member.test
new file mode 100644 (file)
index 0000000..6d883b6
--- /dev/null
@@ -0,0 +1,11 @@
+Invalid Code
+
+errordomain FooError {
+    FAIL
+}
+
+void main () {
+    var e = new FooError.FAIL ("foo");
+    with (e) {
+    }
+}
diff --git a/tests/semantic/with-error.test b/tests/semantic/with-error.test
new file mode 100644 (file)
index 0000000..61cc28e
--- /dev/null
@@ -0,0 +1,10 @@
+Invalid Code
+
+errordomain FooError {
+    FAIL
+}
+
+void main () {
+    with (FooError) {
+    }
+}
diff --git a/tests/semantic/with-namespace.test b/tests/semantic/with-namespace.test
new file mode 100644 (file)
index 0000000..e328fe2
--- /dev/null
@@ -0,0 +1,9 @@
+Invalid Code
+
+namespace Foo {
+}
+
+void main () {
+    with (Foo) {
+    }
+}
diff --git a/tests/semantic/with-no-declaration.test b/tests/semantic/with-no-declaration.test
new file mode 100644 (file)
index 0000000..3b033a8
--- /dev/null
@@ -0,0 +1,9 @@
+Invalid Code
+
+class Foo {
+}
+
+void main () {
+    with (f = new Foo ()) {
+    }
+}
diff --git a/tests/semantic/with-no-such-member.test b/tests/semantic/with-no-such-member.test
new file mode 100644 (file)
index 0000000..5f20d21
--- /dev/null
@@ -0,0 +1,10 @@
+Invalid Code
+
+class Foo {
+}
+
+void main () {
+    with (new Foo ()) {
+        field = 100;
+    }
+}
diff --git a/tests/semantic/with-null.vala b/tests/semantic/with-null.vala
new file mode 100644 (file)
index 0000000..dd108b2
--- /dev/null
@@ -0,0 +1,12 @@
+class Foo {
+    public int i;
+}
+
+void main () {
+    Foo? f = null;
+    Process.exit (0);
+
+    with (f) {
+        i = 7;
+    }
+}
diff --git a/tests/semantic/with-outside-declaration.test b/tests/semantic/with-outside-declaration.test
new file mode 100644 (file)
index 0000000..86af413
--- /dev/null
@@ -0,0 +1,13 @@
+Invalid Code
+
+class Foo {
+    public int i;
+}
+
+void main () {
+    with (var f = new Foo ()) {
+        i = 10;
+    }
+
+    f.i = 100;
+}
diff --git a/tests/semantic/with-pointer.test b/tests/semantic/with-pointer.test
new file mode 100644 (file)
index 0000000..d268b1c
--- /dev/null
@@ -0,0 +1,7 @@
+Invalid Code
+
+void main () {
+    int i = 0;
+    with (&i) {
+    }
+}
diff --git a/tests/semantic/with-string.vala b/tests/semantic/with-string.vala
new file mode 100644 (file)
index 0000000..133be17
--- /dev/null
@@ -0,0 +1,5 @@
+void main () {
+    with ("string ") {
+        assert (chomp () == "string");
+    }
+}
diff --git a/tests/semantic/with-value.vala b/tests/semantic/with-value.vala
new file mode 100644 (file)
index 0000000..a0f111a
--- /dev/null
@@ -0,0 +1,10 @@
+void main () {
+    int i = 0;
+    string s;
+
+    with (i) {
+        s = i.to_string ();
+    }
+
+    assert (s == "0");
+}
index c833939a3d4d31f54e90380be5a6b3e438ca4dd8..0191fc9aa3a0fe0644652cbb6c2f09115141a5be 100644 (file)
@@ -183,6 +183,7 @@ libvala_la_VALASOURCES = \
        valaversionattribute.vala \
        valavoidtype.vala \
        valawhilestatement.vala \
+       valawithstatement.vala \
        valayieldstatement.vala \
        $(NULL)
 
index d961f85e93730a4777490e2df77ff5a82a249c1f..a18e0e7fd23c6807b277d677b1ce9ca45ac500d0 100644 (file)
@@ -404,6 +404,14 @@ public abstract class Vala.CodeVisitor {
        public virtual void visit_unlock_statement (UnlockStatement stmt) {
        }
 
+       /**
+        * Visit operation called for with statements.
+        *
+        * @param stmt a with statement
+        */
+       public virtual void visit_with_statement (WithStatement stmt) {
+       }
+
        /**
         * Visit operation called for delete statements.
         *
index 28ad9fd3471b285ac3dbfcf949e25df7896e51b4..9658cdb1fec8bfe3b7c44d82f833b0ba23ef46de 100644 (file)
@@ -1161,6 +1161,16 @@ public class Vala.CodeWriter : CodeVisitor {
                write_newline ();
        }
 
+       public override void visit_with_statement (WithStatement stmt) {
+               write_indent ();
+               write_string ("with (");
+               stmt.expression.accept (this);
+               write_string (")");
+               stmt.body.accept (this);
+               write_string (";");
+               write_newline ();
+       }
+
        public override void visit_yield_statement (YieldStatement y) {
                write_indent ();
                write_string ("yield");
index 6f58dd3a0e2f3e639f53236920ff4a095ccc68ef..907c219eadcedecddb8e7cc55e2fe32d1cc6f4cb 100644 (file)
@@ -599,6 +599,18 @@ public class Vala.FlowAnalyzer : CodeVisitor {
                }
        }
 
+       public override void visit_with_statement (WithStatement stmt) {
+               if (unreachable (stmt)) {
+                       return;
+               }
+
+               current_block.add_node (stmt.expression);
+
+               handle_errors (stmt.expression);
+
+               stmt.body.accept_children (this);
+       }
+
        public override void visit_if_statement (IfStatement stmt) {
                if (unreachable (stmt)) {
                        return;
index 1bb805026f77f8d5233aa6c3820e052af2c7d88d..1154d3c8b4305fee4a9aaadbbdf44dd64e1ee047 100644 (file)
@@ -220,6 +220,8 @@ public class Vala.MemberAccess : Expression {
                bool may_access_instance_members = false;
                bool may_access_klass_members = false;
 
+               var visited_types = new ArrayList<DataType> ();
+
                symbol_reference = null;
 
                if (qualified) {
@@ -278,6 +280,23 @@ public class Vala.MemberAccess : Expression {
 
                                symbol_reference = SemanticAnalyzer.symbol_lookup_inherited (sym, member_name);
 
+                               if (symbol_reference == null && sym is WithStatement) {
+                                       unowned WithStatement w = (WithStatement) sym;
+
+                                       var variable_type = w.with_variable.variable_type;
+                                       if (variable_type is PointerType) {
+                                               variable_type = ((PointerType) variable_type).base_type;
+                                       }
+                                       visited_types.add (variable_type);
+
+                                       symbol_reference = variable_type.get_member (member_name);
+                                       if (symbol_reference != null) {
+                                               inner = new MemberAccess (null, w.with_variable.name, source_reference);
+                                               inner.check (context);
+                                               may_access_instance_members = true;
+                                       }
+                               }
+
                                if (symbol_reference == null && sym is TypeSymbol && may_access_instance_members) {
                                        // used for generated to_string methods in enums
                                        symbol_reference = this_parameter.variable_type.get_member (member_name);
@@ -509,7 +528,12 @@ public class Vala.MemberAccess : Expression {
                                }
                        }
 
-                       Report.error (source_reference, "The name `%s' does not exist in the context of `%s'%s".printf (member_name, base_type_name, base_type_package));
+                       string visited_types_string = "";
+                       foreach (var type in visited_types) {
+                               visited_types_string += " or `%s'".printf (type.to_string ());
+                       }
+
+                       Report.error (source_reference, "The name `%s' does not exist in the context of `%s'%s%s".printf (member_name, base_type_name, base_type_package, visited_types_string));
                        value_type = new InvalidType ();
                        return false;
                } else if (symbol_reference.error) {
index 2d7fb3e3c5160af107c63740ffffaf72d2b5ff10..191bc75d55d8e445a34c4ea907a3a8c19f7cd211 100644 (file)
@@ -266,6 +266,7 @@ public class Vala.Parser : CodeVisitor {
                case TokenType.VOLATILE:
                case TokenType.WEAK:
                case TokenType.WHILE:
+               case TokenType.WITH:
                case TokenType.YIELD:
                        next ();
                        return;
@@ -1630,6 +1631,9 @@ public class Vala.Parser : CodeVisitor {
                                case TokenType.DELETE:
                                        stmt = parse_delete_statement ();
                                        break;
+                               case TokenType.WITH:
+                                       stmt = parse_with_statement ();
+                                       break;
                                case TokenType.VAR:
                                        is_decl = true;
                                        parse_local_variable_declarations (block);
@@ -1919,7 +1923,7 @@ public class Vala.Parser : CodeVisitor {
                expect (TokenType.SEMICOLON);
        }
 
-       LocalVariable parse_local_variable (DataType? variable_type) throws ParseError {
+       LocalVariable parse_local_variable (DataType? variable_type, bool expect_initializer = false) throws ParseError {
                var begin = get_location ();
                string id = parse_identifier ();
                var type = parse_inline_array_type (variable_type);
@@ -1928,6 +1932,10 @@ public class Vala.Parser : CodeVisitor {
                Expression initializer = null;
                if (accept (TokenType.ASSIGN)) {
                        initializer = parse_expression ();
+               } else if (expect_initializer) {
+                       report_parse_error (new ParseError.SYNTAX ("expected initializer"));
+                       prev ();
+                       initializer = new InvalidExpression ();
                }
                return new LocalVariable (type, id, initializer, src);
        }
@@ -2275,6 +2283,40 @@ public class Vala.Parser : CodeVisitor {
                return new DeleteStatement (expr, src);
        }
 
+       Statement? parse_with_statement () throws ParseError {
+               var begin = get_location ();
+               expect (TokenType.WITH);
+               expect (TokenType.OPEN_PARENS);
+               var expr_or_decl = get_location ();
+
+               LocalVariable? local = null;
+
+               // Try "with (expr)"
+               Expression expr = parse_expression ();
+               if (!accept (TokenType.CLOSE_PARENS)) {
+                       // Try "with (var identifier = expr)"
+                       rollback (expr_or_decl);
+                       DataType variable_type;
+                       if (accept (TokenType.UNOWNED) && accept (TokenType.VAR)) {
+                               variable_type = new VarType (false);
+                       } else {
+                               rollback (expr_or_decl);
+                               if (accept (TokenType.VAR)) {
+                                       variable_type = new VarType ();
+                               } else {
+                                       variable_type = parse_type (true, true);
+                               }
+                       }
+                       local = parse_local_variable (variable_type, true);
+                       expr = local.initializer;
+                       expect (TokenType.CLOSE_PARENS);
+               }
+
+               var src = get_src (begin);
+               var body = parse_embedded_statement ("with", false);
+               return new WithStatement (local, expr, body, src);
+       }
+
        string parse_attribute_value () throws ParseError {
                switch (current ()) {
                case TokenType.NULL:
index a2e76b4d55b35fe971b2db54105b1984fc9f7c8e..7ad6b3c3f0f435a3dadbb7f2b4fa459760fefb85 100644 (file)
@@ -386,7 +386,14 @@ public class Vala.Scanner {
                                if (matches (begin, "void")) return TokenType.VOID;
                                break;
                        case 'w':
-                               if (matches (begin, "weak")) return TokenType.WEAK;
+                               switch (begin[1]) {
+                               case 'e':
+                                       if (matches (begin, "weak")) return TokenType.WEAK;
+                                       break;
+                               case 'i':
+                                       if (matches (begin, "with")) return TokenType.WITH;
+                                       break;
+                               }
                                break;
                        }
                        break;
index dbe9b1f218dd7c1c9e50c0c02dda10ad0ee22907..28365127a89161875f1947d9756572b1c1ad275b 100644 (file)
@@ -480,6 +480,10 @@ public class Vala.SymbolResolver : CodeVisitor {
                list.accept_children (this);
        }
 
+       public override void visit_with_statement (WithStatement stmt) {
+               stmt.accept_children (this);
+       }
+
        public override void visit_expression_statement (ExpressionStatement stmt) {
                if (stmt.checked) {
                        return;
index 75cf92e6c9b94793a63e11bbcf3ced053e3a0e9b..9cc6d1c746480611b6009890070105984256b762 100644 (file)
@@ -153,6 +153,7 @@ public enum Vala.TokenType {
        VOLATILE,
        WEAK,
        WHILE,
+       WITH,
        YIELD;
 
        public unowned string to_string () {
@@ -286,6 +287,7 @@ public enum Vala.TokenType {
                case VOLATILE: return "`volatile'";
                case WEAK: return "`weak'";
                case WHILE: return "`while'";
+               case WITH: return "`with'";
                case YIELD: return "`yield'";
                default: return "unknown token";
                }
diff --git a/vala/valawithstatement.vala b/vala/valawithstatement.vala
new file mode 100644 (file)
index 0000000..f873e60
--- /dev/null
@@ -0,0 +1,150 @@
+/* valawithtatement.vala
+ *
+ * Copyright (C) 2020 Nick Schrader
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
+ *
+ * Authors:
+ *     Nick Schrader <nick.schrader@mailbox.org>
+ */
+
+public class Vala.WithStatement : Block {
+       /**
+        * Expression representing the type of body's dominant scope.
+        */
+       public Expression expression {
+               get { return _expression; }
+               private set {
+                       _expression = value;
+                       _expression.parent_node = this;
+               }
+       }
+
+       /**
+        * Specifies the with-variable.
+        */
+       public LocalVariable? with_variable { get; private set; }
+
+       /**
+        * The block which dominant scope is type of expression.
+        */
+       public Block body {
+               get { return _body; }
+               private set {
+                       _body = value;
+                       _body.parent_node = this;
+               }
+       }
+
+       Expression _expression;
+       Block _body;
+
+       public WithStatement (LocalVariable? variable, Expression expression, Block body, SourceReference? source_reference = null) {
+               base (source_reference);
+               this.with_variable = variable;
+               this.expression = expression;
+               this.body = body;
+       }
+
+       public override void accept (CodeVisitor visitor) {
+               visitor.visit_with_statement (this);
+       }
+
+       public override void accept_children (CodeVisitor visitor) {
+               if (expression.symbol_reference == with_variable) {
+                       expression.accept (visitor);
+               }
+
+               if (with_variable != null) {
+                       with_variable.accept (visitor);
+               }
+
+               body.accept (visitor);
+       }
+
+       bool is_object_or_value_type (DataType? type) {
+               if (type == null) {
+                       return false;
+               } else if (type is PointerType) {
+                       var pointer_type = (PointerType) type;
+                       return is_object_or_value_type (pointer_type.base_type) && expression is PointerIndirection;
+               } else {
+                       return type is ObjectType || type is ValueType;
+               }
+       }
+
+       public override bool check (CodeContext context) {
+               if (checked) {
+                       return !error;
+               }
+
+               checked = true;
+
+               if (!expression.check (context)) {
+                       error = true;
+                       return false;
+               }
+
+               if (!is_object_or_value_type (expression.value_type)) {
+                       error = true;
+                       Report.error (expression.source_reference, "with statement expects an object or basic type");
+                       return false;
+               }
+
+               var local_var = expression.symbol_reference as LocalVariable;
+               if (with_variable != null || local_var == null) {
+                       if (with_variable == null) {
+                               local_var = new LocalVariable (expression.value_type.copy (), get_temp_name (), expression, source_reference);
+                       } else {
+                               local_var = with_variable;
+                       }
+                       body.insert_statement (0, new DeclarationStatement (local_var, source_reference));
+               }
+               with_variable = local_var;
+
+               var old_symbol = context.analyzer.current_symbol;
+               owner = context.analyzer.current_symbol.scope;
+               context.analyzer.current_symbol = this;
+
+               if (!body.check (context)) {
+                       error = true;
+               }
+
+               context.analyzer.current_symbol = old_symbol;
+
+               return !error;
+       }
+
+       public override void emit (CodeGenerator codegen) {
+               if (expression.symbol_reference == with_variable) {
+                       expression.emit (codegen);
+               }
+               body.emit (codegen);
+       }
+
+       public override void get_error_types (Collection<DataType> collection, SourceReference? source_reference = null) {
+               if (source_reference == null) {
+                       source_reference = this.source_reference;
+               }
+               expression.get_error_types (collection, source_reference);
+               body.get_error_types (collection, source_reference);
+       }
+
+       public override void get_defined_variables (Collection<Variable> collection) {
+               if (expression.symbol_reference != with_variable) {
+                       collection.add (with_variable);
+               }
+       }
+}