Index: src/AST/Convert.cpp
===================================================================
--- src/AST/Convert.cpp	(revision 89c2f7c92369856ac89bc39ac4e4671b2ac71fb3)
+++ src/AST/Convert.cpp	(revision 54e41b38e2be1b9c4d667e9c0fe4eb202e951fc7)
@@ -413,8 +413,4 @@
 	}
 
-	virtual void visit( AttrExpr * ) override final {
-
-	}
-
 	virtual void visit( LogicalExpr * ) override final {
 
Index: src/AST/Decl.hpp
===================================================================
--- src/AST/Decl.hpp	(revision 89c2f7c92369856ac89bc39ac4e4671b2ac71fb3)
+++ src/AST/Decl.hpp	(revision 54e41b38e2be1b9c4d667e9c0fe4eb202e951fc7)
@@ -32,9 +32,4 @@
 namespace ast {
 
-class Attribute;
-class Expr;
-class Init;
-class TypeDecl;
-
 /// Base declaration class
 class Decl : public ParseNode {
@@ -149,10 +144,10 @@
 public:
 	ptr<Type> base;
-	std::vector<ptr<TypeDecl>> parameters;
+	std::vector<ptr<TypeDecl>> params;
 	std::vector<ptr<DeclWithType>> assertions;
 
 	NamedTypeDecl( const CodeLocation& loc, const std::string& name, Storage::Classes storage,
 		Type* b, Linkage::Spec spec = Linkage::Cforall )
-	: Decl( loc, name, storage, spec ), base( b ), parameters(), assertions() {}
+	: Decl( loc, name, storage, spec ), base( b ), params(), assertions() {}
 
 	/// Produces a name for the kind of alias
@@ -230,5 +225,5 @@
 public:
 	std::vector<ptr<Decl>> members;
-	std::vector<ptr<TypeDecl>> parameters;
+	std::vector<ptr<TypeDecl>> params;
 	std::vector<ptr<Attribute>> attributes;
 	bool body = false;
@@ -237,5 +232,5 @@
 	AggregateDecl( const CodeLocation& loc, const std::string& name,
 		std::vector<ptr<Attribute>>&& attrs = {}, Linkage::Spec linkage = Linkage::Cforall )
-	: Decl( loc, name, Storage::Classes{}, linkage ), members(), parameters(),
+	: Decl( loc, name, Storage::Classes{}, linkage ), members(), params(),
 	  attributes( std::move(attrs) ) {}
 
Index: src/AST/Expr.cpp
===================================================================
--- src/AST/Expr.cpp	(revision 54e41b38e2be1b9c4d667e9c0fe4eb202e951fc7)
+++ src/AST/Expr.cpp	(revision 54e41b38e2be1b9c4d667e9c0fe4eb202e951fc7)
@@ -0,0 +1,295 @@
+//
+// Cforall Version 1.0.0 Copyright (C) 2015 University of Waterloo
+//
+// The contents of this file are covered under the licence agreement in the
+// file "LICENCE" distributed with Cforall.
+//
+// Expr.cpp --
+//
+// Author           : Aaron B. Moss
+// Created On       : Wed May 15 17:00:00 2019
+// Last Modified By : Aaron B. Moss
+// Created On       : Wed May 15 17:00:00 2019
+// Update Count     : 1
+//
+
+#include "Expr.hpp"
+
+#include <cassert>                 // for strict_dynamic_cast
+#include <string>                  // for to_string
+#include <vector>
+
+#include "Type.hpp"
+#include "Common/SemanticError.h"
+#include "GenPoly/Lvalue.h"        // for referencesPermissable
+#include "InitTweak/InitTweak.h"   // for getPointerBase
+#include "ResolvExpr/typeops.h"    // for extractResultType
+
+namespace ast {
+
+// --- ApplicationExpr
+
+ApplicationExpr::ApplicationExpr( const CodeLocation & loc, const Expr * f, 
+	std::vector<ptr<Expr>> && as ) 
+: Expr( loc ), func( f ), args( std::move(args) ) {
+	// ensure that `ApplicationExpr` result type is `FuncExpr`
+	const PointerType * pt = strict_dynamic_cast< const PointerType * >( f->result.get() );
+	const FunctionType * fn = strict_dynamic_cast< const FunctionType * >( pt->base.get() );
+
+	result = ResolvExpr::extractResultType( fn );
+	assert( result );
+}
+
+// --- UntypedExpr
+
+UntypedExpr * UntypedExpr::createDeref( const CodeLocation & loc, Expr * arg ) {
+	assert( arg );
+
+	UntypedExpr * ret = new UntypedExpr{ 
+		loc, new NameExpr{loc, "*?"}, std::vector<ptr<Expr>>{ ptr<Expr>{ arg } } 
+	};
+	if ( const Type * ty = arg->result ) {
+		const Type * base = InitTweak::getPointerBase( ty );
+		assertf( base, "expected pointer type in dereference (type was %s)", toString( ty ).c_str() );
+
+		if ( GenPoly::referencesPermissable() ) {
+			// if references are still allowed in the AST, dereference returns a reference
+			ret->result = new ReferenceType{ base };
+		} else {
+			// references have been removed, in which case dereference returns an lvalue of the 
+			// base type
+			ret->result.set_and_mutate( base )->set_lvalue( true );
+		}
+	}
+	return ret;
+}
+
+UntypedExpr * UntypedExpr::createAssign( const CodeLocation & loc, Expr * lhs, Expr * rhs ) {
+	assert( lhs && rhs );
+
+	UntypedExpr * ret = new UntypedExpr{
+		loc, new NameExpr{loc, "?=?"}, std::vector<ptr<Expr>>{ ptr<Expr>{ lhs }, ptr<Expr>{ rhs } }
+	};
+	if ( lhs->result && rhs->result ) {
+		// if both expressions are typed, assumes that this assignment is a C bitwise assignment,
+		// so the result is the type of the RHS
+		ret->result = rhs->result;
+	}
+	return ret;
+}
+
+// --- AddressExpr
+
+// Address expressions are typed based on the following inference rules:
+//    E : lvalue T  &..& (n references)
+//   &E :        T *&..& (n references)
+//
+//    E : T  &..&        (m references)
+//   &E : T *&..&        (m-1 references)
+
+namespace {
+	/// The type of the address of a type.
+	/// Caller is responsible for managing returned memory
+	Type * addrType( const Type * type ) {
+		if ( const ReferenceType * refType = dynamic_cast< const ReferenceType * >( type ) ) {
+			CV::Qualifiers quals = refType->qualifiers;
+			return new ReferenceType{ addrType( refType->base ), refType->qualifiers };
+		} else {
+			return new PointerType{ type };
+		}
+	}
+}
+
+AddressExpr::AddressExpr( const CodeLocation & loc, const Expr * a ) : Expr( loc ), arg( a ) {
+	if ( arg->result ) {
+		if ( arg->result->is_lvalue() ) {
+			// lvalue, retains all levels of reference, and gains a pointer inside the references
+			Type * res = addrType( arg->result );
+			res->set_lvalue( false ); // result of & is never an lvalue
+			result = res;
+		} else {
+			// taking address of non-lvalue, must be a reference, loses one layer of reference
+			if ( const ReferenceType * refType = 
+					dynamic_cast< const ReferenceType * >( arg->result.get() ) ) {
+				Type * res = addrType( refType->base );
+				res->set_lvalue( false ); // result of & is never an lvalue
+				result = res;
+			} else {
+				SemanticError( loc, arg->result, 
+					"Attempt to take address of non-lvalue expression: " );
+			}
+		}
+	}
+}
+
+// --- LabelAddressExpr
+
+// label address always has type `void*`
+LabelAddressExpr::LabelAddressExpr( const CodeLocation & loc, Label && a ) 
+: Expr( loc, new PointerType{ new VoidType{} } ), arg( a ) {}
+
+// --- CastExpr
+
+CastExpr::CastExpr( const CodeLocation & loc, const Expr * a, GeneratedFlag g ) 
+: Expr( loc, new VoidType{} ), arg( a ), isGenerated( g ) {}
+
+// --- KeywordCastExpr
+
+const std::string & KeywordCastExpr::targetString() const {
+	static const std::string targetStrs[] = {
+		"coroutine", "thread", "monitor"
+	};
+	static_assert(
+		(sizeof(targetStrs) / sizeof(targetStrs[0])) == ((unsigned long)NUMBER_OF_TARGETS),
+		"Each KeywordCastExpr::Target should have a corresponding string representation"
+	);
+	return targetStrs[(unsigned long)target];
+}
+
+// --- MemberExpr
+
+MemberExpr::MemberExpr( const CodeLocation & loc, const DeclWithType * mem, const Expr * agg )
+: Expr( loc ), member( mem ), aggregate( agg ) {
+	assert( member );
+	assert( aggregate );
+	assert( aggregate->result );
+
+	assert(!"unimplemented; need TypeSubstitution, genericSubstitution");
+}
+
+// --- VariableExpr
+
+VariableExpr::VariableExpr( const CodeLocation & loc, const DeclWithType * v )
+: Expr( loc ), var( v ) {
+	assert( var );
+	assert( var->get_type() );
+	result.set_and_mutate( var->get_type() )->set_lvalue( true );
+}
+
+VariableExpr * VariableExpr::functionPointer( 
+		const CodeLocation & loc, const FunctionDecl * decl ) {
+	// wrap usually-determined result type in a pointer
+	VariableExpr * funcExpr = new VariableExpr{ loc, decl };
+	funcExpr->result = new PointerType{ funcExpr->result };
+	return funcExpr;
+}
+
+// --- ConstantExpr
+
+long long int ConstantExpr::intValue() const {
+	if ( const BasicType * bty = result.as< BasicType >() ) {
+		if ( bty->isInteger() ) {
+			return val.ival;
+		}
+	} else if ( result.as< ZeroType >() ) {
+		return 0;
+	} else if ( result.as< OneType >() ) {
+		return 1;
+	}
+	SemanticError( this, "Constant expression of non-integral type " );
+}
+
+double ConstantExpr::floatValue() const {
+	if ( const BasicType * bty = result.as< BasicType >() ) {
+		if ( ! bty->isInteger() ) {
+			return val.dval;
+		}
+	}
+	SemanticError( this, "Constant expression of non-floating-point type " );
+}
+
+ConstantExpr * ConstantExpr::from_bool( const CodeLocation & loc, bool b ) {
+	return new ConstantExpr{ 
+		loc, new BasicType{ BasicType::Bool }, b ? "1" : "0", (unsigned long long)b };
+}
+
+ConstantExpr * ConstantExpr::from_char( const CodeLocation & loc, char c ) {
+	return new ConstantExpr{ 
+		loc, new BasicType{ BasicType::Char }, std::to_string( c ), (unsigned long long)c };
+}
+
+ConstantExpr * ConstantExpr::from_int( const CodeLocation & loc, int i ) {
+	return new ConstantExpr{ 
+		loc, new BasicType{ BasicType::SignedInt }, std::to_string( i ), (unsigned long long)i };
+}
+
+ConstantExpr * ConstantExpr::from_ulong( const CodeLocation & loc, unsigned long i ) {
+	return new ConstantExpr{ 
+		loc, new BasicType{ BasicType::LongUnsignedInt }, std::to_string( i ), 
+		(unsigned long long)i };
+}
+
+ConstantExpr * ConstantExpr::from_double( const CodeLocation & loc, double d ) {
+	return new ConstantExpr{ loc, new BasicType{ BasicType::Double }, std::to_string( d ), d };
+}
+
+ConstantExpr * ConstantExpr::from_string( const CodeLocation & loc, const std::string & s ) {
+	return new ConstantExpr{
+		loc,
+		new ArrayType{ 
+			new BasicType{ BasicType::Char, CV::Const },
+			ConstantExpr::from_int( loc, s.size() + 1 /* null terminator */ ),
+			FixedLen, DynamicDim },
+		std::string{"\""} + s + "\"",
+		(unsigned long long)0 };
+}
+
+ConstantExpr * ConstantExpr::null( const CodeLocation & loc, const Type * ptrType ) {
+	return new ConstantExpr{
+		loc, ptrType ? ptrType : new PointerType{ new VoidType{} }, "0", (unsigned long long)0 };
+}
+
+// --- SizeofExpr
+
+SizeofExpr::SizeofExpr( const CodeLocation & loc, const Expr * e )
+: Expr( loc, new BasicType{ BasicType::LongUnsignedInt } ), expr( e ), type( nullptr ) {}
+
+SizeofExpr::SizeofExpr( const CodeLocation & loc, const Type * t )
+: Expr( loc, new BasicType{ BasicType::LongUnsignedInt } ), expr( nullptr ), type( t ) {}
+
+// --- AlignofExpr
+
+AlignofExpr::AlignofExpr( const CodeLocation & loc, const Expr * e )
+: Expr( loc, new BasicType{ BasicType::LongUnsignedInt } ), expr( e ), type( nullptr ) {}
+
+AlignofExpr::AlignofExpr( const CodeLocation & loc, const Type * t )
+: Expr( loc, new BasicType{ BasicType::LongUnsignedInt } ), expr( nullptr ), type( t ) {}
+
+// --- UntypedOffsetofExpr
+
+UntypedOffsetofExpr::UntypedOffsetofExpr( 
+	const CodeLocation & loc, const Type * ty, const std::string & mem )
+: Expr( loc, new BasicType{ BasicType::LongUnsignedInt } ), type( ty ), member( mem ) {
+	assert( type );
+}
+
+// --- OffsetofExpr
+
+OffsetofExpr::OffsetofExpr( const CodeLocation & loc, const Type * ty, const DeclWithType * mem )
+: Expr( loc, new BasicType{ BasicType::LongUnsignedInt } ), type( ty ), member( mem ) {
+	assert( type );
+	assert( member );
+}
+
+// --- OffsetPackExpr
+
+OffsetPackExpr::OffsetPackExpr( const CodeLocation & loc, const StructInstType * ty )
+: Expr( loc, new ArrayType{ 
+	new BasicType{ BasicType::LongUnsignedInt }, nullptr, FixedLen, DynamicDim } 
+), type( ty ) {
+	assert( type );
+}
+
+// --- LogicalExpr
+
+LogicalExpr::LogicalExpr( 
+	const CodeLocation & loc, const Expr * a1, const Expr * a2, LogicalFlag ia )
+: Expr( loc, new BasicType{ BasicType::SignedInt } ), arg1( a1 ), arg2( a2 ), isAnd( ia ) {}
+
+}
+
+// Local Variables: //
+// tab-width: 4 //
+// mode: c++ //
+// compile-command: "make install" //
+// End: //
Index: src/AST/Expr.hpp
===================================================================
--- src/AST/Expr.hpp	(revision 89c2f7c92369856ac89bc39ac4e4671b2ac71fb3)
+++ src/AST/Expr.hpp	(revision 54e41b38e2be1b9c4d667e9c0fe4eb202e951fc7)
@@ -18,8 +18,10 @@
 #include <cassert>
 #include <map>
+#include <string>
 #include <utility>        // for move
 #include <vector>
 
 #include "Fwd.hpp"        // for UniqueId
+#include "Label.hpp"
 #include "ParseNode.hpp"
 #include "Visitor.hpp"
@@ -117,5 +119,6 @@
 	bool extension = false;
 
-	Expr(const CodeLocation & loc ) : ParseNode( loc ), result(), env(), inferred() {}
+	Expr( const CodeLocation & loc, const Type * res = nullptr )
+	: ParseNode( loc ), result( res ), env(), inferred() {}
 
 	Expr * set_extension( bool ex ) { extension = ex; return this; }
@@ -124,4 +127,324 @@
 private:
 	Expr * clone() const override = 0;
+};
+
+/// The application of a function to a set of parameters. 
+/// Post-resolver form of `UntypedExpr`
+class ApplicationExpr final : public Expr {
+public:
+	ptr<Expr> func;
+	std::vector<ptr<Expr>> args;
+
+	ApplicationExpr( const CodeLocation & loc, const Expr * f, std::vector<ptr<Expr>> && as = {} );
+
+	const Expr * accept( Visitor & v ) const override { return v.visit( this ); }
+private:
+	ApplicationExpr * clone() const override { return new ApplicationExpr{ *this }; }
+};
+
+/// The application of a function to a set of parameters, pre-overload resolution.
+class UntypedExpr final : public Expr {
+public:
+	ptr<Expr> func;
+	std::vector<ptr<Expr>> args;
+
+	UntypedExpr( const CodeLocation & loc, const Expr * f, std::vector<ptr<Expr>> && as = {} )
+	: Expr( loc ), func( f ), args( std::move(as) ) {}
+
+	/// Creates a new dereference expression
+	static UntypedExpr * createDeref( const CodeLocation & loc, Expr * arg );
+	/// Creates a new assignment expression
+	static UntypedExpr * createAssign( const CodeLocation & loc, Expr * lhs, Expr * rhs );
+
+	const Expr * accept( Visitor & v ) const override { return v.visit( this ); }
+private:
+	UntypedExpr * clone() const override { return new UntypedExpr{ *this }; }
+};
+
+/// A name whose name is as-yet undetermined.
+/// May also be used to avoid name mangling in codegen phase.
+class NameExpr final : public Expr {
+public:
+	std::string name;
+
+	NameExpr( const CodeLocation & loc, const std::string & n ) : Expr( loc ), name( n ) {}
+
+	const Expr * accept( Visitor & v ) const override { return v.visit( this ); }
+private:
+	NameExpr * clone() const override { return new NameExpr{ *this }; }
+};
+
+/// Address-of expression `&e`
+class AddressExpr final : public Expr {
+public:
+	ptr<Expr> arg;
+
+	AddressExpr( const CodeLocation & loc, const Expr * a );
+
+	const Expr * accept( Visitor & v ) const override { return v.visit( this ); }
+private:
+	AddressExpr * clone() const override { return new AddressExpr{ *this }; }
+};
+
+/// GCC &&label
+/// https://gcc.gnu.org/onlinedocs/gcc-3.4.2/gcc/Labels-as-Values.html
+class LabelAddressExpr final : public Expr {
+public:
+	Label arg;
+
+	LabelAddressExpr( const CodeLocation & loc, Label && a );
+
+	const Expr * accept( Visitor & v ) const override { return v.visit( this ); }
+private:
+	LabelAddressExpr * clone() const override { return new LabelAddressExpr{ *this }; }
+};
+
+/// Whether a cast existed in the program source or not
+enum GeneratedFlag { ExplicitCast, GeneratedCast };
+
+/// A type cast, e.g. `(int)e`
+class CastExpr final : public Expr {
+public:
+	ptr<Expr> arg;
+	GeneratedFlag isGenerated;
+
+	CastExpr( const CodeLocation & loc, const Expr * a, const Type * to, 
+		GeneratedFlag g = GeneratedCast ) : Expr( loc, to ), arg( a ), isGenerated( g ) {}
+	/// Cast-to-void
+	CastExpr( const CodeLocation & loc, const Expr * a, GeneratedFlag g = GeneratedCast );
+
+	const Expr * accept( Visitor & v ) const override { return v.visit( this ); }
+private:
+	CastExpr * clone() const override { return new CastExpr{ *this }; }
+};
+
+/// A cast to "keyword types", e.g. `(thread &)t`
+class KeywordCastExpr final : public Expr {
+public:
+	ptr<Expr> arg;
+	enum Target { Coroutine, Thread, Monitor, NUMBER_OF_TARGETS } target;
+
+	KeywordCastExpr( const CodeLocation & loc, const Expr * a, Target t )
+	: Expr( loc ), arg( a ), target( t ) {}
+
+	/// Get a name for the target type
+	const std::string& targetString() const;
+
+	const Expr * accept( Visitor & v ) const override { return v.visit( this ); }
+private:
+	KeywordCastExpr * clone() const override { return new KeywordCastExpr{ *this }; }
+};
+
+/// A virtual dynamic cast, e.g. `(virtual exception)e`
+class VirtualCastExpr final : public Expr {
+public:
+	ptr<Expr> arg;
+
+	VirtualCastExpr( const CodeLocation & loc, const Expr * a, const Type * to )
+	: Expr( loc, to ), arg( a ) {}
+
+	const Expr * accept( Visitor & v ) const override { return v.visit( this ); }
+private:
+	VirtualCastExpr * clone() const override { return new VirtualCastExpr{ *this }; }
+};
+
+/// A member selection operation before expression resolution, e.g. `q.p`
+class UntypedMemberExpr final : public Expr {
+public:
+	ptr<Expr> member;
+	ptr<Expr> aggregate;
+
+	UntypedMemberExpr( const CodeLocation & loc, const Expr * mem, const Expr * agg )
+	: Expr( loc ), member( mem ), aggregate( agg ) { assert( aggregate ); }
+
+	const Expr * accept( Visitor & v ) const override { return v.visit( this ); }
+private:
+	UntypedMemberExpr * clone() const override { return new UntypedMemberExpr{ *this }; }
+};
+
+/// A member selection operation after expression resolution, e.g. `q.p`
+class MemberExpr final : public Expr {
+public:
+	readonly<DeclWithType> member;
+	ptr<Expr> aggregate;
+
+	MemberExpr( const CodeLocation & loc, const DeclWithType * mem, const Expr * agg );
+
+	const Expr * accept( Visitor & v ) const override { return v.visit( this ); }
+private:
+	MemberExpr * clone() const override { return new MemberExpr{ *this }; }
+};
+
+/// A reference to a named variable.
+class VariableExpr final : public Expr {
+public:
+	readonly<DeclWithType> var;
+
+	VariableExpr( const CodeLocation & loc, const DeclWithType * v );
+
+	/// generates a function pointer for a given function
+	static VariableExpr * functionPointer( const CodeLocation & loc, const FunctionDecl * decl );
+
+	const Expr * accept( Visitor & v ) const override { return v.visit( this ); }
+private:
+	VariableExpr * clone() const override { return new VariableExpr{ *this }; }
+};
+
+/// A compile-time constant
+class ConstantExpr final : public Expr {
+	union Val {
+		unsigned long long ival;
+		double dval;
+		
+		Val( unsigned long long i ) : ival( i ) {}
+		Val( double d ) : dval( d ) {}
+	} val;
+public:
+	std::string rep;
+
+	ConstantExpr( 
+		const CodeLocation & loc, const Type * ty, const std::string & r, unsigned long long v )
+	: Expr( loc, ty ), val( v ), rep( r ) {}
+	ConstantExpr( const CodeLocation & loc, const Type * ty, const std::string & r, double v )
+	: Expr( loc, ty ), val( v ), rep( r ) {}
+	
+	/// Gets the value of this constant as an integer
+	long long int intValue() const;
+	/// Gets the value of this constant as floating point
+	double floatValue() const;
+
+	/// generates a boolean constant of the given bool
+	static ConstantExpr * from_bool( const CodeLocation & loc, bool b );
+	/// generates a char constant of the given char
+	static ConstantExpr * from_char( const CodeLocation & loc, char c );
+	/// generates an integer constant of the given int
+	static ConstantExpr * from_int( const CodeLocation & loc, int i );
+	/// generates an integer constant of the given unsigned long int
+	static ConstantExpr * from_ulong( const CodeLocation & loc, unsigned long i );
+	/// generates a floating point constant of the given double
+	static ConstantExpr * from_double( const CodeLocation & loc, double d );
+	/// generates an array of chars constant of the given string
+	static ConstantExpr * from_string( const CodeLocation & loc, const std::string & s );
+	/// generates a null pointer value for the given type. void * if omitted.
+	static ConstantExpr * null( const CodeLocation & loc, const Type * ptrType = nullptr );
+
+	const Expr * accept( Visitor & v ) const override { return v.visit( this ); }
+private:
+	ConstantExpr * clone() const override { return new ConstantExpr{ *this }; }
+};
+
+/// sizeof expression, e.g. `sizeof(int)`, `sizeof 3+4`
+class SizeofExpr final : public Expr {
+public:
+	ptr<Expr> expr;
+	ptr<Type> type;
+
+	SizeofExpr( const CodeLocation & loc, const Expr * e );
+	SizeofExpr( const CodeLocation & loc, const Type * t );
+	// deliberately no disambiguating overload for nullptr_t
+
+	const Expr * accept( Visitor & v ) const override { return v.visit( this ); }
+private:
+	SizeofExpr * clone() const override { return new SizeofExpr{ *this }; }
+};
+
+/// alignof expression, e.g. `alignof(int)`, `alignof 3+4`
+class AlignofExpr final : public Expr {
+public:
+	ptr<Expr> expr;
+	ptr<Type> type;
+
+	AlignofExpr( const CodeLocation & loc, const Expr * e );
+	AlignofExpr( const CodeLocation & loc, const Type * t );
+	// deliberately no disambiguating overload for nullptr_t
+
+	const Expr * accept( Visitor & v ) const override { return v.visit( this ); }
+private:
+	AlignofExpr * clone() const override { return new AlignofExpr{ *this }; }
+};
+
+/// offsetof expression before resolver determines field, e.g. `offsetof(MyStruct, myfield)`
+class UntypedOffsetofExpr final : public Expr {
+public:
+	ptr<Type> type;
+	std::string member;
+
+	UntypedOffsetofExpr( const CodeLocation & loc, const Type * ty, const std::string & mem )
+	: Expr( loc ), type( ty ), member( mem ) {}
+
+	const Expr * accept( Visitor & v ) const override { return v.visit( this ); }
+private:
+	UntypedOffsetofExpr * clone() const override { return new UntypedOffsetofExpr{ *this }; }
+};
+
+/// offsetof expression after resolver determines field, e.g. `offsetof(MyStruct, myfield)`
+class OffsetofExpr final : public Expr {
+public:
+	ptr<Type> type;
+	readonly<DeclWithType> member;
+
+	OffsetofExpr( const CodeLocation & loc, const Type * ty, const DeclWithType * mem );
+
+	const Expr * accept( Visitor & v ) const override { return v.visit( this ); }
+private:
+	OffsetofExpr * clone() const override { return new OffsetofExpr{ *this }; }
+};
+
+/// a pack of field-offsets for a generic type
+class OffsetPackExpr final : public Expr {
+public:
+	ptr<StructInstType> type;
+
+	OffsetPackExpr( const CodeLocation & loc, const StructInstType * ty );
+
+	const Expr * accept( Visitor & v ) const override { return v.visit( this ); }
+private:
+	OffsetPackExpr * clone() const override { return new OffsetPackExpr{ *this }; }
+};
+
+/// Variants of short-circuiting logical expression
+enum LogicalFlag { OrExpr, AndExpr };
+
+/// Short-circuiting boolean expression (`&&` or `||`)
+class LogicalExpr final : public Expr {
+public:
+	ptr<Expr> arg1;
+	ptr<Expr> arg2;
+	LogicalFlag isAnd;
+
+	LogicalExpr( const CodeLocation & loc, const Expr * a1, const Expr * a2, LogicalFlag ia );
+
+	const Expr * accept( Visitor & v ) const override { return v.visit( this ); }
+private:
+	LogicalExpr * clone() const override { return new LogicalExpr{ *this }; }
+};
+
+/// Three-argument conditional e.g. `p ? a : b`
+class ConditionalExpr final : public Expr {
+public:
+	ptr<Expr> arg1;
+	ptr<Expr> arg2;
+	ptr<Expr> arg3;
+
+	ConditionalExpr( const CodeLocation & loc, const Expr * a1, const Expr * a2, const Expr * a3 )
+	: Expr( loc ), arg1( a1 ), arg2( a2 ), arg3( a3 ) {}
+
+	const Expr * accept( Visitor & v ) const override { return v.visit( this ); }
+private:
+	ConditionalExpr * clone() const override { return new ConditionalExpr{ *this }; }
+};
+
+/// Comma expression e.g. `( a , b )`
+class CommaExpr final : public Expr {
+public:
+	ptr<Expr> arg1;
+	ptr<Expr> arg2;
+
+	CommaExpr( const CodeLocation & loc, const Expr * a1, const Expr * a2 ) 
+	: Expr( loc ), arg1( a1 ), arg2( a2 ) {}
+
+	const Expr * accept( Visitor & v ) const override { return v.visit( this ); }
+private:
+	CommaExpr * clone() const override { return new CommaExpr{ *this }; }
 };
 
@@ -138,4 +461,11 @@
 };
 
+/// A GCC "asm constraint operand" used in an asm statement, e.g. `[output] "=f" (result)`.
+/// https://gcc.gnu.org/onlinedocs/gcc-4.7.1/gcc/Machine-Constraints.html#Machine-Constraints
+class AsmExpr final : public Expr {
+public:
+	ptr<Expr> inout;
+	ptr<Expr> constraint; 
+};
 
 //=================================================================================================
@@ -146,50 +476,48 @@
 inline void increment( const class Expr * node, Node::ref_type ref ) { node->increment(ref); }
 inline void decrement( const class Expr * node, Node::ref_type ref ) { node->decrement(ref); }
-// inline void increment( const class ApplicationExpr * node, Node::ref_type ref ) { node->increment(ref); }
-// inline void decrement( const class ApplicationExpr * node, Node::ref_type ref ) { node->decrement(ref); }
-// inline void increment( const class UntypedExpr * node, Node::ref_type ref ) { node->increment(ref); }
-// inline void decrement( const class UntypedExpr * node, Node::ref_type ref ) { node->decrement(ref); }
-// inline void increment( const class NameExpr * node, Node::ref_type ref ) { node->increment(ref); }
-// inline void decrement( const class NameExpr * node, Node::ref_type ref ) { node->decrement(ref); }
-// inline void increment( const class AddressExpr * node, Node::ref_type ref ) { node->increment(ref); }
-// inline void decrement( const class AddressExpr * node, Node::ref_type ref ) { node->decrement(ref); }
-// inline void increment( const class LabelAddressExpr * node, Node::ref_type ref ) { node->increment(ref); }
-// inline void decrement( const class LabelAddressExpr * node, Node::ref_type ref ) { node->decrement(ref); }
-// inline void increment( const class CastExpr * node, Node::ref_type ref ) { node->increment(ref); }
-// inline void decrement( const class CastExpr * node, Node::ref_type ref ) { node->decrement(ref); }
-// inline void increment( const class KeywordCastExpr * node, Node::ref_type ref ) { node->increment(ref); }
-// inline void decrement( const class KeywordCastExpr * node, Node::ref_type ref ) { node->decrement(ref); }
-// inline void increment( const class VirtualCastExpr * node, Node::ref_type ref ) { node->increment(ref); }
-// inline void decrement( const class VirtualCastExpr * node, Node::ref_type ref ) { node->decrement(ref); }
-// inline void increment( const class MemberExpr * node, Node::ref_type ref ) { node->increment(ref); }
-// inline void decrement( const class MemberExpr * node, Node::ref_type ref ) { node->decrement(ref); }
-// inline void increment( const class UntypedMemberExpr * node, Node::ref_type ref ) { node->increment(ref); }
-// inline void decrement( const class UntypedMemberExpr * node, Node::ref_type ref ) { node->decrement(ref); }
-// inline void increment( const class VariableExpr * node, Node::ref_type ref ) { node->increment(ref); }
-// inline void decrement( const class VariableExpr * node, Node::ref_type ref ) { node->decrement(ref); }
-// inline void increment( const class ConstantExpr * node, Node::ref_type ref ) { node->increment(ref); }
-// inline void decrement( const class ConstantExpr * node, Node::ref_type ref ) { node->decrement(ref); }
-// inline void increment( const class SizeofExpr * node, Node::ref_type ref ) { node->increment(ref); }
-// inline void decrement( const class SizeofExpr * node, Node::ref_type ref ) { node->decrement(ref); }
-// inline void increment( const class AlignofExpr * node, Node::ref_type ref ) { node->increment(ref); }
-// inline void decrement( const class AlignofExpr * node, Node::ref_type ref ) { node->decrement(ref); }
-// inline void increment( const class UntypedOffsetofExpr * node, Node::ref_type ref ) { node->increment(ref); }
-// inline void decrement( const class UntypedOffsetofExpr * node, Node::ref_type ref ) { node->decrement(ref); }
-// inline void increment( const class OffsetofExpr * node, Node::ref_type ref ) { node->increment(ref); }
-// inline void decrement( const class OffsetofExpr * node, Node::ref_type ref ) { node->decrement(ref); }
-// inline void increment( const class OffsetPackExpr * node, Node::ref_type ref ) { node->increment(ref); }
-// inline void decrement( const class OffsetPackExpr * node, Node::ref_type ref ) { node->decrement(ref); }
-// inline void increment( const class AttrExpr * node, Node::ref_type ref ) { node->increment(ref); }
-// inline void decrement( const class AttrExpr * node, Node::ref_type ref ) { node->decrement(ref); }
-// inline void increment( const class LogicalExpr * node, Node::ref_type ref ) { node->increment(ref); }
-// inline void decrement( const class LogicalExpr * node, Node::ref_type ref ) { node->decrement(ref); }
-// inline void increment( const class ConditionalExpr * node, Node::ref_type ref ) { node->increment(ref); }
-// inline void decrement( const class ConditionalExpr * node, Node::ref_type ref ) { node->decrement(ref); }
-// inline void increment( const class CommaExpr * node, Node::ref_type ref ) { node->increment(ref); }
-// inline void decrement( const class CommaExpr * node, Node::ref_type ref ) { node->decrement(ref); }
-// inline void increment( const class TypeExpr * node, Node::ref_type ref ) { node->increment(ref); }
-// inline void decrement( const class TypeExpr * node, Node::ref_type ref ) { node->decrement(ref); }
-// inline void increment( const class AsmExpr * node, Node::ref_type ref ) { node->increment(ref); }
-// inline void decrement( const class AsmExpr * node, Node::ref_type ref ) { node->decrement(ref); }
+inline void increment( const class ApplicationExpr * node, Node::ref_type ref ) { node->increment(ref); }
+inline void decrement( const class ApplicationExpr * node, Node::ref_type ref ) { node->decrement(ref); }
+inline void increment( const class UntypedExpr * node, Node::ref_type ref ) { node->increment(ref); }
+inline void decrement( const class UntypedExpr * node, Node::ref_type ref ) { node->decrement(ref); }
+inline void increment( const class NameExpr * node, Node::ref_type ref ) { node->increment(ref); }
+inline void decrement( const class NameExpr * node, Node::ref_type ref ) { node->decrement(ref); }
+inline void increment( const class AddressExpr * node, Node::ref_type ref ) { node->increment(ref); }
+inline void decrement( const class AddressExpr * node, Node::ref_type ref ) { node->decrement(ref); }
+inline void increment( const class LabelAddressExpr * node, Node::ref_type ref ) { node->increment(ref); }
+inline void decrement( const class LabelAddressExpr * node, Node::ref_type ref ) { node->decrement(ref); }
+inline void increment( const class CastExpr * node, Node::ref_type ref ) { node->increment(ref); }
+inline void decrement( const class CastExpr * node, Node::ref_type ref ) { node->decrement(ref); }
+inline void increment( const class KeywordCastExpr * node, Node::ref_type ref ) { node->increment(ref); }
+inline void decrement( const class KeywordCastExpr * node, Node::ref_type ref ) { node->decrement(ref); }
+inline void increment( const class VirtualCastExpr * node, Node::ref_type ref ) { node->increment(ref); }
+inline void decrement( const class VirtualCastExpr * node, Node::ref_type ref ) { node->decrement(ref); }
+inline void increment( const class MemberExpr * node, Node::ref_type ref ) { node->increment(ref); }
+inline void decrement( const class MemberExpr * node, Node::ref_type ref ) { node->decrement(ref); }
+inline void increment( const class UntypedMemberExpr * node, Node::ref_type ref ) { node->increment(ref); }
+inline void decrement( const class UntypedMemberExpr * node, Node::ref_type ref ) { node->decrement(ref); }
+inline void increment( const class VariableExpr * node, Node::ref_type ref ) { node->increment(ref); }
+inline void decrement( const class VariableExpr * node, Node::ref_type ref ) { node->decrement(ref); }
+inline void increment( const class ConstantExpr * node, Node::ref_type ref ) { node->increment(ref); }
+inline void decrement( const class ConstantExpr * node, Node::ref_type ref ) { node->decrement(ref); }
+inline void increment( const class SizeofExpr * node, Node::ref_type ref ) { node->increment(ref); }
+inline void decrement( const class SizeofExpr * node, Node::ref_type ref ) { node->decrement(ref); }
+inline void increment( const class AlignofExpr * node, Node::ref_type ref ) { node->increment(ref); }
+inline void decrement( const class AlignofExpr * node, Node::ref_type ref ) { node->decrement(ref); }
+inline void increment( const class UntypedOffsetofExpr * node, Node::ref_type ref ) { node->increment(ref); }
+inline void decrement( const class UntypedOffsetofExpr * node, Node::ref_type ref ) { node->decrement(ref); }
+inline void increment( const class OffsetofExpr * node, Node::ref_type ref ) { node->increment(ref); }
+inline void decrement( const class OffsetofExpr * node, Node::ref_type ref ) { node->decrement(ref); }
+inline void increment( const class OffsetPackExpr * node, Node::ref_type ref ) { node->increment(ref); }
+inline void decrement( const class OffsetPackExpr * node, Node::ref_type ref ) { node->decrement(ref); }
+inline void increment( const class LogicalExpr * node, Node::ref_type ref ) { node->increment(ref); }
+inline void decrement( const class LogicalExpr * node, Node::ref_type ref ) { node->decrement(ref); }
+inline void increment( const class ConditionalExpr * node, Node::ref_type ref ) { node->increment(ref); }
+inline void decrement( const class ConditionalExpr * node, Node::ref_type ref ) { node->decrement(ref); }
+inline void increment( const class CommaExpr * node, Node::ref_type ref ) { node->increment(ref); }
+inline void decrement( const class CommaExpr * node, Node::ref_type ref ) { node->decrement(ref); }
+inline void increment( const class TypeExpr * node, Node::ref_type ref ) { node->increment(ref); }
+inline void decrement( const class TypeExpr * node, Node::ref_type ref ) { node->decrement(ref); }
+inline void increment( const class AsmExpr * node, Node::ref_type ref ) { node->increment(ref); }
+inline void decrement( const class AsmExpr * node, Node::ref_type ref ) { node->decrement(ref); }
 // inline void increment( const class ImplicitCopyCtorExpr * node, Node::ref_type ref ) { node->increment(ref); }
 // inline void decrement( const class ImplicitCopyCtorExpr * node, Node::ref_type ref ) { node->decrement(ref); }
Index: src/AST/Fwd.hpp
===================================================================
--- src/AST/Fwd.hpp	(revision 89c2f7c92369856ac89bc39ac4e4671b2ac71fb3)
+++ src/AST/Fwd.hpp	(revision 54e41b38e2be1b9c4d667e9c0fe4eb202e951fc7)
@@ -77,5 +77,4 @@
 class OffsetofExpr;
 class OffsetPackExpr;
-class AttrExpr;
 class LogicalExpr;
 class ConditionalExpr;
@@ -252,6 +251,4 @@
 inline void increment( const class OffsetPackExpr *, Node::ref_type );
 inline void decrement( const class OffsetPackExpr *, Node::ref_type );
-inline void increment( const class AttrExpr *, Node::ref_type );
-inline void decrement( const class AttrExpr *, Node::ref_type );
 inline void increment( const class LogicalExpr *, Node::ref_type );
 inline void decrement( const class LogicalExpr *, Node::ref_type );
Index: src/AST/Node.hpp
===================================================================
--- src/AST/Node.hpp	(revision 89c2f7c92369856ac89bc39ac4e4671b2ac71fb3)
+++ src/AST/Node.hpp	(revision 54e41b38e2be1b9c4d667e9c0fe4eb202e951fc7)
@@ -76,5 +76,5 @@
 // problems and be able to use auto return
 template<typename node_t>
-auto mutate(const node_t * node) {
+auto mutate( const node_t * node ) {
 	assertf(
 		node->strong_count >= 1,
@@ -92,5 +92,5 @@
 }
 
-std::ostream& operator<< ( std::ostream& out, const Node* node );
+std::ostream& operator<< ( std::ostream& out, const Node * node );
 
 /// Base class for the smart pointer types
@@ -137,6 +137,19 @@
 	operator const node_t * () const { return node; }
 
+	/// wrapper for convenient access to dynamic_cast
 	template<typename o_node_t>
 	const o_node_t * as() const { return dynamic_cast<const o_node_t *>(node); }
+
+	/// Sets this pointer to a mutated version of a pointer (possibly) owned elsehere.
+	/// Returns a mutable version of the pointer in this node.
+	node_t * set_and_mutate( const node_t * n ) {
+		// ensure ownership of `n` by this node to avoid spurious single-owner mutates
+		assign( n );
+		// get mutable version of `n`
+		auto r = mutate( node ); 
+		// re-assign mutable version in case `mutate()` produced a new pointer
+		assign( r );
+		return r;
+	}
 
 	using ptr = const node_t *;
Index: src/AST/Type.hpp
===================================================================
--- src/AST/Type.hpp	(revision 89c2f7c92369856ac89bc39ac4e4671b2ac71fb3)
+++ src/AST/Type.hpp	(revision 54e41b38e2be1b9c4d667e9c0fe4eb202e951fc7)
@@ -32,22 +32,21 @@
 
 class Type : public Node {
-	CV::Qualifiers tq;
-public:
-	Type( CV::Qualifiers q = {} ) : tq(q) {}
-
-	CV::Qualifiers qualifiers() const { return tq; }
-	bool is_const() const { return tq.is_const; }
-	bool is_volatile() const { return tq.is_volatile; }
-	bool is_restrict() const { return tq.is_restrict; }
-	bool is_lvalue() const { return tq.is_lvalue; }
-	bool is_mutex() const { return tq.is_mutex; }
-	bool is_atomic() const { return tq.is_atomic; }
-
-	void set_qualifiers( CV::Qualifiers q ) { tq = q; }
-	void set_const( bool v ) { tq.is_const = v; }
-	void set_restrict( bool v ) { tq.is_restrict = v; }
-	void set_lvalue( bool v ) { tq.is_lvalue = v; }
-	void set_mutex( bool v ) { tq.is_mutex = v; }
-	void set_atomic( bool v ) { tq.is_atomic = v; }
+public:
+	CV::Qualifiers qualifiers;
+	
+	Type( CV::Qualifiers q = {} ) : qualifiers(q) {}
+
+	bool is_const() const { return qualifiers.is_const; }
+	bool is_volatile() const { return qualifiers.is_volatile; }
+	bool is_restrict() const { return qualifiers.is_restrict; }
+	bool is_lvalue() const { return qualifiers.is_lvalue; }
+	bool is_mutex() const { return qualifiers.is_mutex; }
+	bool is_atomic() const { return qualifiers.is_atomic; }
+
+	void set_const( bool v ) { qualifiers.is_const = v; }
+	void set_restrict( bool v ) { qualifiers.is_restrict = v; }
+	void set_lvalue( bool v ) { qualifiers.is_lvalue = v; }
+	void set_mutex( bool v ) { qualifiers.is_mutex = v; }
+	void set_atomic( bool v ) { qualifiers.is_atomic = v; }
 
 	/// How many elemental types are represented by this type
@@ -254,6 +253,6 @@
 class FunctionType final : public ParameterizedType {
 public:
-	std::vector<ptr<DeclWithType>> returnVals;
-	std::vector<ptr<DeclWithType>> parameters;
+	std::vector<ptr<DeclWithType>> returns;
+	std::vector<ptr<DeclWithType>> params;
 
 	/// Does the function accept a variable number of arguments following the arguments specified 
@@ -265,10 +264,10 @@
 
 	FunctionType( ArgumentFlag va = FixedArgs, CV::Qualifiers q = {} )
-	: ParameterizedType(q), returnVals(), parameters(), isVarArgs(va) {}
+	: ParameterizedType(q), returns(), params(), isVarArgs(va) {}
 
 	/// true if either the parameters or return values contain a tttype
 	bool isTtype() const;
 	/// true if function parameters are unconstrained by prototype
-	bool isUnprototyped() const { return isVarArgs && parameters.size() == 0; }
+	bool isUnprototyped() const { return isVarArgs && params.size() == 0; }
 
 	const Type * accept( Visitor & v ) const override { return v.visit( this ); }
@@ -280,5 +279,5 @@
 class ReferenceToType : public ParameterizedType {
 public:
-	std::vector<ptr<Expr>> parameters;
+	std::vector<ptr<Expr>> params;
 	std::vector<ptr<Attribute>> attributes;
 	std::string name;
@@ -287,5 +286,5 @@
 	ReferenceToType( const std::string& n, CV::Qualifiers q = {}, 
 		std::vector<ptr<Attribute>> && as = {} )
-	: ParameterizedType(q), parameters(), attributes(std::move(as)), name(n) {}
+	: ParameterizedType(q), params(), attributes(std::move(as)), name(n) {}
 
 	/// Gets aggregate declaration this type refers to
Index: src/AST/Visitor.hpp
===================================================================
--- src/AST/Visitor.hpp	(revision 89c2f7c92369856ac89bc39ac4e4671b2ac71fb3)
+++ src/AST/Visitor.hpp	(revision 54e41b38e2be1b9c4d667e9c0fe4eb202e951fc7)
@@ -69,5 +69,4 @@
     virtual const ast::Expr *             visit( const ast::OffsetofExpr         * ) = 0;
     virtual const ast::Expr *             visit( const ast::OffsetPackExpr       * ) = 0;
-    virtual const ast::Expr *             visit( const ast::AttrExpr             * ) = 0;
     virtual const ast::Expr *             visit( const ast::LogicalExpr          * ) = 0;
     virtual const ast::Expr *             visit( const ast::ConditionalExpr      * ) = 0;
Index: src/AST/porting.md
===================================================================
--- src/AST/porting.md	(revision 89c2f7c92369856ac89bc39ac4e4671b2ac71fb3)
+++ src/AST/porting.md	(revision 54e41b38e2be1b9c4d667e9c0fe4eb202e951fc7)
@@ -6,5 +6,23 @@
   * specialization: strong pointer `ast::ptr<T>` is used for an ownership relationship
   * specialization: weak pointer `ast::readonly<T>` is used for an observation relationship
-  * added `ast::ptr_base<T,R>::as<S>()` with same semantics as `dynamic_cast<S*>(p)`
+* added `ast::ptr_base<T,R>::as<S>()` with same semantics as `dynamic_cast<S*>(p)`
+* added `N * ast::ptr_base<N,R>::set_and_mutate( const N * n )`
+  * takes ownership of `n`, then returns a mutable version owned by this pointer
+  * Some debate on whether this is a good approach:
+    * makes an easy path to cloning, which we were trying to eliminate
+      * counter-point: these are all mutating clones rather than lifetime-preserving clones, and thus "necessary" (for some definition)
+    * existing uses:
+      * `VariableExpr::VariableExpr`, `UntypedExpr::createDeref`
+        * both involve grabbing a type from elsewhere and making an `lvalue` copy of it
+        * could potentially be replaced by a view class something like this:
+          ```
+          template<unsigned Quals>
+          class AddQualifiersType final : public Type {
+            readonly<Type> base;
+            // ...
+          };
+          ```
+          * requires all `qualifiers` use (and related helpers) to be virtual, non-zero overhead
+          * also subtle semantic change, where mutations to the source decl now change the viewing expression
 
 ## Visitors ##
@@ -106,6 +124,12 @@
   * allows `newObject` as just default settings
 
+`NamedTypeDecl`
+* `parameters` => `params`
+
 `TypeDecl`
 * moved `TypeDecl::Kind` to `ast::TypeVar::Kind`
+
+`AggregateDecl`
+* `parameters` => `params`
 
 `EnumDecl`
@@ -115,4 +139,30 @@
 * Merged `inferParams`/`resnSlots` into union, as suggested by comment in old version
   * does imply get_/set_ API, and some care about moving backward
+* added constructor that sets result, for benefit of types that set it directly
+
+`ApplicationExpr`
+* `function` => `func`
+
+`UntypedExpr`
+* `function` => `func`
+* removed `begin_args()` in favour of `args.begin()`
+
+`MemberExpr`
+* **TODO** port setup of `result` in constructor
+
+`ConstantExpr`
+* inlined features of `Constant`, never used elsewhere, so removed `Constant`
+  * `Constant Constant::from_int(int)` etc. => `ConstantExpr * ConstantExpr::from_int(CodeLocation, int)`
+    * allocates new `ConstantExpr`, consistent with all existing uses
+
+`SizeofExpr`, `AlignofExpr`
+* `isType` deprecated in favour of boolean check on `type`
+  * all existing uses assume `type` set if true and don't use `expr`
+
+`AttrExpr`
+* did not port due to feature deprecation (e.g. `expr@attribute`)
+
+`LogicalExpr`
+* un-defaulted constructor parameter determining `&&` or `||`
 
 `Init`
@@ -148,4 +198,5 @@
 `Type`
 * `CV::Qualifiers` moved to end of constructor parameter list, defaulted to `{}`
+  * removed getter, setter in favour of public `qualifiers` field
   * `ReferenceToType` puts a defaulted list of attributes after qualifiers
 * `forall` field split off into `ParameterizedType` subclass
@@ -160,5 +211,5 @@
   * `getAggr()` => `aggr()`
     * also now returns `const AggregateDecl *`
-* `genericSubstitution()` moved to own visitor **TODO** write
+* `genericSubstitution()` moved to own visitor in `AST/GenericSubstitution.hpp` **TODO** write
 
 `BasicType`
@@ -167,5 +218,6 @@
 `ReferenceToType`
 * deleted `get_baseParameters()` from children
-  * replace with `aggr() ? aggr()->parameters : nullptr`
+  * replace with `aggr() ? aggr()->params : nullptr`
+* `parameters` => `params`
 * hoisted `lookup` implementation into parent, made non-virtual
   * also changed to return vector rather than filling; change back if any great win for reuse
@@ -178,4 +230,6 @@
 
 `FunctionType`
+* `returnVals` => `returns`
+* `parameters` => `params`
 * `bool isVarArgs;` => `enum ArgumentFlag { FixedArgs, VariableArgs }; ArgumentFlag isVarArgs;`
 
Index: src/ResolvExpr/Unify.cc
===================================================================
--- src/ResolvExpr/Unify.cc	(revision 89c2f7c92369856ac89bc39ac4e4671b2ac71fb3)
+++ src/ResolvExpr/Unify.cc	(revision 54e41b38e2be1b9c4d667e9c0fe4eb202e951fc7)
@@ -21,5 +21,8 @@
 #include <string>                 // for string, operator==, operator!=, bas...
 #include <utility>                // for pair, move
-
+#include <vector>
+
+#include "AST/Node.hpp"
+#include "AST/Type.hpp"
 #include "Common/PassVisitor.h"   // for PassVisitor
 #include "FindOpenVars.h"         // for findOpenVars
@@ -630,5 +633,4 @@
 	}
 
-	// xxx - compute once and store in the FunctionType?
 	Type * extractResultType( FunctionType * function ) {
 		if ( function->get_returnVals().size() == 0 ) {
@@ -644,4 +646,16 @@
 		}
 	}
+
+	ast::ptr<ast::Type> extractResultType( const ast::FunctionType * func ) {
+		assert(!"restore after AST added to build");
+		// if ( func->returns.empty() ) return new ast::VoidType{};
+		// if ( func->returns.size() == 1 ) return func->returns[0]->get_type();
+
+		// std::vector<ast::ptr<ast::Type>> tys;
+		// for ( const ast::DeclWithType * decl : func->returns ) {
+		// 	tys.emplace_back( decl->get_type() );
+		// }
+		// return new ast::TupleType{ std::move(tys) };
+	}
 } // namespace ResolvExpr
 
Index: src/ResolvExpr/typeops.h
===================================================================
--- src/ResolvExpr/typeops.h	(revision 89c2f7c92369856ac89bc39ac4e4671b2ac71fb3)
+++ src/ResolvExpr/typeops.h	(revision 54e41b38e2be1b9c4d667e9c0fe4eb202e951fc7)
@@ -18,4 +18,6 @@
 #include <vector>
 
+#include "AST/Node.hpp"
+#include "AST/Type.hpp"
 #include "SynTree/SynTree.h"
 #include "SynTree/Type.h"
@@ -99,4 +101,6 @@
 	/// creates the type represented by the list of returnVals in a FunctionType. The caller owns the return value.
 	Type * extractResultType( FunctionType * functionType );
+	/// Creates or extracts the type represented by the list of returns in a `FunctionType`.
+	ast::ptr<ast::Type> extractResultType( const ast::FunctionType * func );
 
 	// in CommonType.cc
