[vala/staging: 2/2] Add support for "with" statement



commit 35f72f3ecdf0d61d0e67cd4f72c29fee2c3c9a55
Author: Nick Schrader <nick schrader mailbox org>
Date:   Fri Mar 27 13:12:09 2020 -0300

    Add support for "with" statement
    
    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

 tests/Makefile.am                               |  30 +++++
 tests/nullability/with-non-null.test            |  12 ++
 tests/objects/with-expression.vala              |  38 ++++++
 tests/objects/with-instance.vala                |  73 ++++++++++++
 tests/objects/with-nested.vala                  |  34 ++++++
 tests/parser/with-embedded.vala                 |   4 +
 tests/parser/with-empty.vala                    |   3 +
 tests/parser/with-invalid-declaration.test      |   6 +
 tests/parser/with-invalid-expression.test       |   6 +
 tests/parser/with-no-block.test                 |   5 +
 tests/parser/with-no-expression.test            |   6 +
 tests/semantic/with-array.test                  |   7 ++
 tests/semantic/with-buildin.vala                |   5 +
 tests/semantic/with-class.test                  |   9 ++
 tests/semantic/with-compact.vala                |  13 ++
 tests/semantic/with-declaration-cast-type.vala  |  10 ++
 tests/semantic/with-declaration-wrong-type.test |  12 ++
 tests/semantic/with-declaration.vala            |  35 ++++++
 tests/semantic/with-dereferenced-pointer.vala   |  14 +++
 tests/semantic/with-enum-member.vala            |   8 ++
 tests/semantic/with-enum.test                   |  10 ++
 tests/semantic/with-error-member.test           |  11 ++
 tests/semantic/with-error.test                  |  10 ++
 tests/semantic/with-namespace.test              |   9 ++
 tests/semantic/with-no-declaration.test         |   9 ++
 tests/semantic/with-no-such-member.test         |  10 ++
 tests/semantic/with-null.vala                   |  12 ++
 tests/semantic/with-outside-declaration.test    |  13 ++
 tests/semantic/with-pointer.test                |   7 ++
 tests/semantic/with-string.vala                 |   5 +
 tests/semantic/with-value.vala                  |  10 ++
 vala/Makefile.am                                |   1 +
 vala/valacodevisitor.vala                       |   8 ++
 vala/valacodewriter.vala                        |  10 ++
 vala/valaflowanalyzer.vala                      |  12 ++
 vala/valamemberaccess.vala                      |  26 +++-
 vala/valaparser.vala                            |  44 ++++++-
 vala/valascanner.vala                           |   9 +-
 vala/valasymbolresolver.vala                    |   4 +
 vala/valatokentype.vala                         |   2 +
 vala/valawithstatement.vala                     | 150 ++++++++++++++++++++++++
 41 files changed, 699 insertions(+), 3 deletions(-)
---
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 263e8975e..24e8964fa 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -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
index 000000000..ed8fb40df
--- /dev/null
+++ b/tests/nullability/with-non-null.test
@@ -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
index 000000000..8e2821ded
--- /dev/null
+++ b/tests/objects/with-expression.vala
@@ -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
index 000000000..715833ef8
--- /dev/null
+++ b/tests/objects/with-instance.vala
@@ -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
index 000000000..ac356654e
--- /dev/null
+++ b/tests/objects/with-nested.vala
@@ -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
index 000000000..c668e5afc
--- /dev/null
+++ b/tests/parser/with-embedded.vala
@@ -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
index 000000000..1a870b61f
--- /dev/null
+++ b/tests/parser/with-empty.vala
@@ -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
index 000000000..ae1e775b2
--- /dev/null
+++ b/tests/parser/with-invalid-declaration.test
@@ -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
index 000000000..50fbe77e3
--- /dev/null
+++ b/tests/parser/with-invalid-expression.test
@@ -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
index 000000000..237bd89d8
--- /dev/null
+++ b/tests/parser/with-no-block.test
@@ -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
index 000000000..0e01d28ab
--- /dev/null
+++ b/tests/parser/with-no-expression.test
@@ -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
index 000000000..bf0b4d18d
--- /dev/null
+++ b/tests/semantic/with-array.test
@@ -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
index 000000000..999554adf
--- /dev/null
+++ b/tests/semantic/with-buildin.vala
@@ -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
index 000000000..2bea6e52c
--- /dev/null
+++ b/tests/semantic/with-class.test
@@ -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
index 000000000..b2530c91f
--- /dev/null
+++ b/tests/semantic/with-compact.vala
@@ -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
index 000000000..c855d1c0b
--- /dev/null
+++ b/tests/semantic/with-declaration-cast-type.vala
@@ -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
index 000000000..79ae8f8fb
--- /dev/null
+++ b/tests/semantic/with-declaration-wrong-type.test
@@ -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
index 000000000..78763c333
--- /dev/null
+++ b/tests/semantic/with-declaration.vala
@@ -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
index 000000000..42fc20fd8
--- /dev/null
+++ b/tests/semantic/with-dereferenced-pointer.vala
@@ -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
index 000000000..3b5517db0
--- /dev/null
+++ b/tests/semantic/with-enum-member.vala
@@ -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
index 000000000..3a473a2f1
--- /dev/null
+++ b/tests/semantic/with-enum.test
@@ -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
index 000000000..6d883b6e5
--- /dev/null
+++ b/tests/semantic/with-error-member.test
@@ -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
index 000000000..61cc28efa
--- /dev/null
+++ b/tests/semantic/with-error.test
@@ -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
index 000000000..e328fe205
--- /dev/null
+++ b/tests/semantic/with-namespace.test
@@ -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
index 000000000..3b033a8e3
--- /dev/null
+++ b/tests/semantic/with-no-declaration.test
@@ -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
index 000000000..5f20d21c8
--- /dev/null
+++ b/tests/semantic/with-no-such-member.test
@@ -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
index 000000000..dd108b256
--- /dev/null
+++ b/tests/semantic/with-null.vala
@@ -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
index 000000000..86af413e7
--- /dev/null
+++ b/tests/semantic/with-outside-declaration.test
@@ -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
index 000000000..d268b1cd2
--- /dev/null
+++ b/tests/semantic/with-pointer.test
@@ -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
index 000000000..133be17b6
--- /dev/null
+++ b/tests/semantic/with-string.vala
@@ -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
index 000000000..a0f111ad8
--- /dev/null
+++ b/tests/semantic/with-value.vala
@@ -0,0 +1,10 @@
+void main () {
+    int i = 0;
+    string s;
+
+    with (i) {
+        s = i.to_string ();
+    }
+
+    assert (s == "0");
+}
diff --git a/vala/Makefile.am b/vala/Makefile.am
index c833939a3..0191fc9aa 100644
--- a/vala/Makefile.am
+++ b/vala/Makefile.am
@@ -183,6 +183,7 @@ libvala_la_VALASOURCES = \
        valaversionattribute.vala \
        valavoidtype.vala \
        valawhilestatement.vala \
+       valawithstatement.vala \
        valayieldstatement.vala \
        $(NULL)
 
diff --git a/vala/valacodevisitor.vala b/vala/valacodevisitor.vala
index d961f85e9..a18e0e7fd 100644
--- a/vala/valacodevisitor.vala
+++ b/vala/valacodevisitor.vala
@@ -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.
         *
diff --git a/vala/valacodewriter.vala b/vala/valacodewriter.vala
index 28ad9fd34..9658cdb1f 100644
--- a/vala/valacodewriter.vala
+++ b/vala/valacodewriter.vala
@@ -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");
diff --git a/vala/valaflowanalyzer.vala b/vala/valaflowanalyzer.vala
index 6f58dd3a0..907c219ea 100644
--- a/vala/valaflowanalyzer.vala
+++ b/vala/valaflowanalyzer.vala
@@ -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;
diff --git a/vala/valamemberaccess.vala b/vala/valamemberaccess.vala
index 1bb805026..1154d3c8b 100644
--- a/vala/valamemberaccess.vala
+++ b/vala/valamemberaccess.vala
@@ -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) {
diff --git a/vala/valaparser.vala b/vala/valaparser.vala
index 2d7fb3e3c..191bc75d5 100644
--- a/vala/valaparser.vala
+++ b/vala/valaparser.vala
@@ -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:
diff --git a/vala/valascanner.vala b/vala/valascanner.vala
index a2e76b4d5..7ad6b3c3f 100644
--- a/vala/valascanner.vala
+++ b/vala/valascanner.vala
@@ -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;
diff --git a/vala/valasymbolresolver.vala b/vala/valasymbolresolver.vala
index dbe9b1f21..28365127a 100644
--- a/vala/valasymbolresolver.vala
+++ b/vala/valasymbolresolver.vala
@@ -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;
diff --git a/vala/valatokentype.vala b/vala/valatokentype.vala
index 75cf92e6c..9cc6d1c74 100644
--- a/vala/valatokentype.vala
+++ b/vala/valatokentype.vala
@@ -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
index 000000000..f873e600c
--- /dev/null
+++ b/vala/valawithstatement.vala
@@ -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);
+               }
+       }
+}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]