[vala/wip/issue/327: 12/12] Add support for "with" statement



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

    Add support for "with" statement
    
    Create data-type scoped blocks
    
    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                            |  43 ++++++-
 vala/valascanner.vala                           |   9 +-
 vala/valasymbolresolver.vala                    |   4 +
 vala/valatokentype.vala                         |   2 +
 vala/valawithstatement.vala                     | 150 ++++++++++++++++++++++++
 41 files changed, 698 insertions(+), 3 deletions(-)
---
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 78c939a2f..7ff4ff350 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -504,6 +504,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 \
@@ -733,6 +736,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 \
@@ -927,6 +936,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 \
@@ -940,6 +969,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 9f0b976fc..e4bc73ac8 100644
--- a/vala/valaparser.vala
+++ b/vala/valaparser.vala
@@ -1617,6 +1617,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);
@@ -1906,7 +1909,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);
@@ -1915,6 +1918,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);
        }
@@ -2262,6 +2269,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]