]> git.lizzy.rs Git - rust.git/commitdiff
Error checking for protocols. We'll need spans though.
authorEric Holk <eric.holk@gmail.com>
Tue, 17 Jul 2012 00:27:04 +0000 (17:27 -0700)
committerEric Holk <eric.holk@gmail.com>
Tue, 17 Jul 2012 17:35:59 +0000 (10:35 -0700)
src/libcore/tuple.rs
src/libsyntax/ext/base.rs
src/libsyntax/ext/pipes.rs
src/libsyntax/ext/pipes/check.rs [new file with mode: 0644]
src/libsyntax/ext/pipes/pipec.rs
src/libsyntax/ext/pipes/proto.rs [new file with mode: 0644]
src/libsyntax/syntax.rc

index 0724d01afaf495f9971b195576073b65da8123c8..040f34ca00bc9fcd5f809155215c1f6209c552d3 100644 (file)
@@ -23,6 +23,29 @@ impl extensions <T:copy, U:copy> for (T, U) {
 
 }
 
+impl extensions<A: copy, B: copy> for (&[A], &[B]) {
+    fn zip() -> ~[(A, B)] {
+        let (a, b) = self;
+        vec::zip(a, b)
+    }
+
+    fn map<C>(f: fn(A, B) -> C) -> ~[C] {
+        let (a, b) = self;
+        vec::map2(a, b, f)
+    }
+}
+
+impl extensions<A: copy, B: copy> for (~[A], ~[B]) {
+    fn zip() -> ~[(A, B)] {
+        let (a, b) = self;
+        vec::zip(a, b)
+    }
+
+    fn map<C>(f: fn(A, B) -> C) -> ~[C] {
+        let (a, b) = self;
+        vec::map2(a, b, f)
+    }
+}
 
 #[test]
 fn test_tuple() {
index b7d17a9d65201ca311e7396db3ccb5ea68173552..f6b15ff19d9d16162bdae8dddba8e19a1770b8b0 100644 (file)
@@ -100,6 +100,7 @@ fn builtin_item_tt(f: syntax_expander_tt_item_) -> syntax_extension {
     fn bt_pop();
     fn span_fatal(sp: span, msg: ~str) -> !;
     fn span_err(sp: span, msg: ~str);
+    fn span_warn(sp: span, msg: ~str);
     fn span_unimpl(sp: span, msg: ~str) -> !;
     fn span_bug(sp: span, msg: ~str) -> !;
     fn bug(msg: ~str) -> !;
@@ -148,6 +149,10 @@ fn span_err(sp: span, msg: ~str) {
             self.print_backtrace();
             self.parse_sess.span_diagnostic.span_err(sp, msg);
         }
+        fn span_warn(sp: span, msg: ~str) {
+            self.print_backtrace();
+            self.parse_sess.span_diagnostic.span_warn(sp, msg);
+        }
         fn span_unimpl(sp: span, msg: ~str) -> ! {
             self.print_backtrace();
             self.parse_sess.span_diagnostic.span_unimpl(sp, msg);
index 3a6a2ff7a5239ba9bfcf00db72b792feb342d172..bea1c8cae0cbe5e9d3f3a47fce5e488ce7db9ca8 100644 (file)
@@ -8,7 +8,9 @@
 
 import pipes::parse_proto::proto_parser;
 
-import pipes::pipec::methods;
+import pipes::pipec::compile;
+import pipes::proto::{visit, protocol};
+import pipes::check::proto_check;
 
 fn expand_proto(cx: ext_ctxt, _sp: span, id: ast::ident,
                 tt: ~[ast::token_tree]) -> base::mac_result
@@ -22,5 +24,9 @@ fn expand_proto(cx: ext_ctxt, _sp: span, id: ast::ident,
 
     let proto = rust_parser.parse_proto(id);
 
+    // check for errors
+    visit(proto, cx);
+
+    // compile
     base::mr_item(proto.compile(cx))
 }
diff --git a/src/libsyntax/ext/pipes/check.rs b/src/libsyntax/ext/pipes/check.rs
new file mode 100644 (file)
index 0000000..7cf9670
--- /dev/null
@@ -0,0 +1,74 @@
+/// Correctness for protocols
+
+/*
+
+This section of code makes sure the protocol is likely to generate
+correct code. The correctness criteria include:
+
+  * No protocols transition to states that don't exist.
+  * Messages step to states with the right number of type parameters.
+
+In addition, this serves as a lint pass. Lint warns for the following
+things.
+
+  * States with no messages, it's better to step to !.
+
+It would also be nice to warn about unreachable states, but the
+visitor infrastructure for protocols doesn't currently work well for
+that.
+
+*/
+
+import dvec::extensions;
+
+import ext::base::ext_ctxt;
+
+import ast::{ident};
+
+import proto::{state, protocol, next_state, methods};
+import ast_builder::empty_span;
+
+impl proto_check of proto::visitor<(), (), ()>  for ext_ctxt {
+    fn visit_proto(_proto: protocol,
+                   _states: &[()]) { }
+
+    fn visit_state(state: state, _m: &[()]) {
+        if state.messages.len() == 0 {
+            self.span_warn(
+                empty_span(), // use a real span!
+                #fmt("state %s contains no messages, \
+                      consider stepping to a terminal state instead",
+                     *state.name))
+        }
+    }
+
+    fn visit_message(name: ident, _tys: &[@ast::ty],
+                     this: state, next: next_state) {
+         alt next {
+           some({state: next, tys: next_tys}) {
+             let proto = this.proto;
+             if !proto.has_state(next) {
+                 // This should be a span fatal, but then we need to
+                 // track span information.
+                 self.span_err(
+                     empty_span(),
+                     #fmt("message %s steps to undefined state, %s",
+                          *name, *next));
+            }
+
+            let next = proto.get_state(next);
+
+            if next.ty_params.len() != next_tys.len() {
+                self.span_err(
+                    empty_span(), // use a real span
+                    #fmt("message %s target (%s) \
+                          needs %u type parameters, but got %u",
+                         *name, *next.name,
+                         next.ty_params.len(),
+                         next_tys.len()));
+            }
+          }
+          none { }
+        }
+    }
+}
\ No newline at end of file
index fa8fd67f0315651df9083bb880dfc7725a687924..4af75fa90e7b2616d46fa057e53ca4b694fde176 100644 (file)
@@ -5,6 +5,8 @@
 import dvec::dvec;
 import dvec::extensions;
 
+import tuple::extensions;
+
 import ast::ident;
 import util::interner;
 import interner::{intern, get};
 import parse;
 import parse::*;
 
+import proto::*;
+
 import ast_builder::ast_builder;
 import ast_builder::methods;
 import ast_builder::path;
 
-enum direction {
-    send, recv
-}
-
-impl of to_str for direction {
-    fn to_str() -> ~str {
-        alt self {
-          send { ~"send" }
-          recv { ~"recv" }
-        }
-    }
-}
-
-impl methods for direction {
-    fn reverse() -> direction {
-        alt self {
-          send { recv }
-          recv { send }
-        }
-    }
-}
-
-type next_state = option<{state: ident, tys: ~[@ast::ty]}>;
-
-enum message {
-    // name, data, current state, next state
-    message(ident, ~[@ast::ty], state, next_state)
-}
-
-impl methods for message {
-    fn name() -> ident {
-        alt self {
-          message(id, _, _, _) {
-            id
-          }
-        }
-    }
-
-    // Return the type parameters actually used by this message
-    fn get_params() -> ~[ast::ty_param] {
-        alt self {
-          message(_, _, this, _) {
-            this.ty_params
-          }
-        }
-    }
-
+impl compile for message {
     fn gen_send(cx: ext_ctxt) -> @ast::item {
         #debug("pipec: gen_send");
         alt self {
@@ -154,34 +112,7 @@ fn gen_send(cx: ext_ctxt) -> @ast::item {
     }
 }
 
-enum state {
-    state_(@{
-        name: ident,
-        dir: direction,
-        ty_params: ~[ast::ty_param],
-        messages: dvec<message>,
-        proto: protocol,
-    }),
-}
-
-impl methods for state {
-    fn add_message(name: ident, +data: ~[@ast::ty], next: next_state) {
-        self.messages.push(message(name, data, self,
-                                   next));
-    }
-
-    fn filename() -> ~str {
-        (*self).proto.filename()
-    }
-
-    fn data_name() -> ident {
-        self.name
-    }
-
-    fn to_ty(cx: ext_ctxt) -> @ast::ty {
-        cx.ty_path(path(self.name).add_tys(cx.ty_vars(self.ty_params)))
-    }
-
+impl compile for state {
     fn to_type_decls(cx: ext_ctxt) -> ~[@ast::item] {
         #debug("pipec: to_type_decls");
         // This compiles into two different type declarations. Say the
@@ -248,47 +179,7 @@ fn to_endpoint_decls(cx: ext_ctxt, dir: direction) -> ~[@ast::item] {
     }
 }
 
-enum protocol {
-    protocol_(@{
-        name: ident,
-        states: dvec<state>,
-    }),
-}
-
-fn protocol(name: ident) -> protocol {
-    protocol_(@{name: name, states: dvec()})
-}
-
-impl methods for protocol {
-    fn add_state(name: ident, dir: direction) -> state {
-        self.add_state_poly(name, dir, ~[])
-    }
-
-    /// Get or create a state.
-    fn get_state(name: ident) -> state {
-        self.states.find(|i| i.name == name).get()
-    }
-
-    fn add_state_poly(name: ident, dir: direction,
-                      +ty_params: ~[ast::ty_param]) -> state {
-        let messages = dvec();
-
-        let state = state_(@{
-            name: name,
-            dir: dir,
-            ty_params: ty_params,
-            messages: messages,
-            proto: self
-        });
-
-        self.states.push(state);
-        state
-    }
-
-    fn filename() -> ~str {
-        ~"proto://" + *self.name
-    }
-
+impl compile for protocol {
     fn gen_init(cx: ext_ctxt) -> @ast::item {
         let start_state = self.states[0];
 
@@ -302,18 +193,12 @@ fn gen_init(cx: ext_ctxt) -> @ast::item {
           }
         };
 
-        parse_item_from_source_str(
-            self.filename(),
-            @#fmt("fn init%s() -> (client::%s, server::%s)\
-                   { %s }",
-                  start_state.ty_params.to_source(),
-                  start_state.to_ty(cx).to_source(),
-                  start_state.to_ty(cx).to_source(),
-                  body.to_source()),
-            cx.cfg(),
-            ~[],
-            ast::public,
-            cx.parse_sess()).get()
+        cx.parse_item(#fmt("fn init%s() -> (client::%s, server::%s)\
+                            { %s }",
+                           start_state.ty_params.to_source(),
+                           start_state.to_ty(cx).to_source(),
+                           start_state.to_ty(cx).to_source(),
+                           body.to_source()))
     }
 
     fn compile(cx: ext_ctxt) -> @ast::item {
@@ -407,15 +292,3 @@ fn parse_expr(s: ~str) -> @ast::expr {
             self.parse_sess())
     }
 }
-
-impl methods<A: copy, B: copy> for (~[A], ~[B]) {
-    fn zip() -> ~[(A, B)] {
-        let (a, b) = self;
-        vec::zip(a, b)
-    }
-
-    fn map<C>(f: fn(A, B) -> C) -> ~[C] {
-        let (a, b) = self;
-        vec::map2(a, b, f)
-    }
-}
diff --git a/src/libsyntax/ext/pipes/proto.rs b/src/libsyntax/ext/pipes/proto.rs
new file mode 100644 (file)
index 0000000..1df3466
--- /dev/null
@@ -0,0 +1,150 @@
+import to_str::to_str;
+import dvec::{dvec, extensions};
+
+import ast::{ident};
+
+import ast_builder::{path, methods, ast_builder};
+
+enum direction {
+    send, recv
+}
+
+impl of to_str for direction {
+    fn to_str() -> ~str {
+        alt self {
+          send { ~"send" }
+          recv { ~"recv" }
+        }
+    }
+}
+
+impl methods for direction {
+    fn reverse() -> direction {
+        alt self {
+          send { recv }
+          recv { send }
+        }
+    }
+}
+
+type next_state = option<{state: ident, tys: ~[@ast::ty]}>;
+
+enum message {
+    // name, data, current state, next state
+    message(ident, ~[@ast::ty], state, next_state)
+}
+
+impl methods for message {
+    fn name() -> ident {
+        alt self {
+          message(id, _, _, _) {
+            id
+          }
+        }
+    }
+
+    /// Return the type parameters actually used by this message
+    fn get_params() -> ~[ast::ty_param] {
+        alt self {
+          message(_, _, this, _) {
+            this.ty_params
+          }
+        }
+    }
+}
+
+enum state {
+    state_(@{
+        name: ident,
+        dir: direction,
+        ty_params: ~[ast::ty_param],
+        messages: dvec<message>,
+        proto: protocol,
+    }),
+}
+
+impl methods for state {
+    fn add_message(name: ident, +data: ~[@ast::ty], next: next_state) {
+        self.messages.push(message(name, data, self,
+                                   next));
+    }
+
+    fn filename() -> ~str {
+        (*self).proto.filename()
+    }
+
+    fn data_name() -> ident {
+        self.name
+    }
+
+    fn to_ty(cx: ext_ctxt) -> @ast::ty {
+        cx.ty_path(path(self.name).add_tys(cx.ty_vars(self.ty_params)))
+    }
+}
+
+enum protocol {
+    protocol_(@{
+        name: ident,
+        states: dvec<state>,
+    }),
+}
+
+fn protocol(name: ident) -> protocol {
+    protocol_(@{name: name, states: dvec()})
+}
+
+impl methods for protocol {
+    fn add_state(name: ident, dir: direction) -> state {
+        self.add_state_poly(name, dir, ~[])
+    }
+
+    /// Get or create a state.
+    fn get_state(name: ident) -> state {
+        self.states.find(|i| i.name == name).get()
+    }
+
+    fn has_state(name: ident) -> bool {
+        self.states.find(|i| i.name == name) != none
+    }
+
+    fn add_state_poly(name: ident, dir: direction,
+                      +ty_params: ~[ast::ty_param]) -> state {
+        let messages = dvec();
+
+        let state = state_(@{
+            name: name,
+            dir: dir,
+            ty_params: ty_params,
+            messages: messages,
+            proto: self
+        });
+
+        self.states.push(state);
+        state
+    }
+
+    fn filename() -> ~str {
+        ~"proto://" + *self.name
+    }
+}
+
+trait visitor<Tproto, Tstate, Tmessage> {
+    fn visit_proto(proto: protocol, st: &[Tstate]) -> Tproto;
+    fn visit_state(state: state, m: &[Tmessage]) -> Tstate;
+    fn visit_message(name: ident, tys: &[@ast::ty],
+                     this: state, next: next_state) -> Tmessage;
+}
+
+fn visit<Tproto, Tstate, Tmessage, V: visitor<Tproto, Tstate, Tmessage>>(
+    proto: protocol, visitor: V) -> Tproto {
+
+    // the copy keywords prevent recursive use of dvec
+    let states = do (copy proto.states).map_to_vec |s| {
+        let messages = do (copy s.messages).map_to_vec |m| {
+            let message(name, tys, this, next) = m;
+            visitor.visit_message(name, tys, this, next)
+        };
+        visitor.visit_state(s, messages)
+    };
+    visitor.visit_proto(proto, states)
+}
index 99a877a82ac198813888bd1e09fe072a6d3e9f4d..9750f2d5bd12ae0cfd7e722041b290603cfe5d18 100644 (file)
@@ -85,5 +85,7 @@ mod ext {
         mod ast_builder;
         mod parse_proto;
         mod pipec;
+        mod proto;
+        mod check;
     }
 }